From 4ae774dc8fb1ff24d8b2c774afc75d875ca721f3 Mon Sep 17 00:00:00 2001 From: Sean Boettger Date: Thu, 19 Feb 2026 18:48:18 +1100 Subject: [PATCH 1/2] Add splashkit-arrays.h header & tests --- coresdk/src/test/test_arrays.cpp | 470 ++++++++++++ coresdk/src/test/test_main.cpp | 1 + coresdk/src/test/test_main.h | 1 + languages/cpp/splashkit-arrays.h | 710 ++++++++++++++++++ projects/cmake/CMakeLists.txt | 2 + .../scripts/cmake/libsplashkit/CMakeLists.txt | 10 +- 6 files changed, 1193 insertions(+), 1 deletion(-) create mode 100644 coresdk/src/test/test_arrays.cpp create mode 100644 languages/cpp/splashkit-arrays.h diff --git a/coresdk/src/test/test_arrays.cpp b/coresdk/src/test/test_arrays.cpp new file mode 100644 index 00000000..85c0a263 --- /dev/null +++ b/coresdk/src/test/test_arrays.cpp @@ -0,0 +1,470 @@ +// This is 90% AI generated, but has been checked +// by a human who thinks it looks 'probably okay?' + +#include +#include +#include + +#include "terminal.h" +#include "basics.h" +using namespace splashkit_lib; + +#include "splashkit-arrays.h" + +namespace bounded_tests +{ + using namespace std; + + int tests_run = 0; + int tests_passed = 0; + + void assert_true(bool condition, const string &message) + { + tests_run++; + if (!condition) + { + cout << "❌ FAILED: " << message << endl; + } + else + { + tests_passed++; + } + } + + template + void assert_throws(Func func, const string &message) + { + tests_run++; + try + { + func(); + cout << "❌ FAILED (no exception): " << message << endl; + } + catch (const ExceptionType &) + { + tests_passed++; + } + catch (...) + { + cout << "❌ FAILED (wrong exception): " << message << endl; + } + } + + void test_basic_int_operations() + { + cout << "\nRunning test_basic_int_operations...\n"; + + bounded_array arr; + + assert_true(arr.length() == 0, "Initial length should be 0"); + assert_true(arr.capacity() == 5, "Capacity should be 5"); + + arr.add(10); + arr.add(20); + arr.add(30); + + assert_true(arr.length() == 3, "Length should be 3 after 3 adds"); + assert_true(arr[0] == 10, "Index 0 should be 10"); + assert_true(arr[1] == 20, "Index 1 should be 20"); + assert_true(arr[2] == 30, "Index 2 should be 30"); + } + + void test_string() + { + cout << "\nRunning test_string...\n"; + + bounded_array arr; + + arr.add("Hello"); + add(arr, "world"); + arr[1] += "!"; + get(arr, 1) += "!"; + + assert_true(arr[1] == "world!!", "Index 1 should equal 'world!!'"); + } + + void test_capacity_and_exceptions() + { + cout << "\nRunning test_capacity_and_exceptions...\n"; + + bounded_array arr; + + arr.add(1); + arr.add(2); + + assert_true(arr.length() == 2, "Length should be 2"); + + assert_throws( + [&]() { arr.add(3); }, + "Adding past capacity should throw array_full" + ); + + assert_throws( + [&]() { arr.get(-1); }, + "Accessing negative index should throw" + ); + + assert_throws( + [&]() { arr.get(2); }, + "Accessing index >= size should throw" + ); + + assert_throws( + [&]() { arr.remove(5); }, + "Removing invalid index should throw" + ); + } + + + void test_remove_and_shifting() + { + cout << "\nRunning test_remove_and_shifting...\n"; + + bounded_array arr; + + arr.add(10); + arr.add(20); + arr.add(30); + arr.add(40); + + arr.remove(1); // remove 20 + + assert_true(arr.length() == 3, "Length should decrease after remove"); + assert_true(arr[0] == 10, "Index 0 should remain 10"); + assert_true(arr[1] == 30, "Index 1 should now be 30"); + assert_true(arr[2] == 40, "Index 2 should now be 40"); + + arr.remove(0); // remove 10 + + assert_true(arr[0] == 30, "After removing first element"); + assert_true(arr.length() == 2, "Length should be 2"); + } + + + void test_accessor_variants() + { + cout << "\nRunning test_accessor_variants...\n"; + + bounded_array arr; + add(arr, 5); + add(arr, 10); + + assert_true(length(arr) == 2, "Free length() should work"); + assert_true(capacity(arr) == 3, "Free capacity() should work"); + assert_true(get(arr, 1) == 10, "Free get() should work"); + + const bounded_array& const_arr = arr; + + assert_true(const_arr[0] == 5, "Const operator[] should work"); + assert_true(get(const_arr, 0) == 5, "Const free get() should work"); + } + + + struct Person + { + string name; + int age; + + bool operator==(const Person &other) const + { + return name == other.name && age == other.age; + } + }; + + void test_complex_types() + { + cout << "\nRunning test_complex_types...\n"; + + bounded_array people; + + people.add({"Alice", 25}); + people.add({"Bob", 30}); + + assert_true(people.length() == 2, "People length should be 2"); + assert_true(people[0] == Person{"Alice", 25}, "First person correct"); + + people[1].age = 31; + assert_true(people[1].age == 31, "Modifying via operator[] should work"); + } + + void test_nested_arrays() + { + cout << "\nRunning test_nested_arrays...\n"; + + bounded_array< bounded_array, 2 > outer; + + bounded_array inner1; + inner1.add(1); + inner1.add(2); + + bounded_array inner2; + inner2.add(10); + + outer.add(inner1); + outer.add(inner2); + + assert_true(outer.length() == 2, "Outer length should be 2"); + assert_true(outer[0][0] == 1, "Nested access should work"); + assert_true(outer[0][1] == 2, "Nested access should work"); + assert_true(outer[1][0] == 10, "Nested access should work"); + + outer[0].remove(0); + assert_true(outer[0][0] == 2, "Nested remove should shift correctly"); + } + void run_all_tests() + { + test_basic_int_operations(); + test_string(); + test_capacity_and_exceptions(); + test_remove_and_shifting(); + test_accessor_variants(); + test_complex_types(); + test_nested_arrays(); + + cout << "\n=========================\n"; + cout << "Tests passed: " << tests_passed << " / " << tests_run << endl; + cout << "=========================\n"; + } +} + +namespace dynamic_tests +{ + using namespace std; + + int tests_run = 0; + int tests_passed = 0; + + void assert_true(bool condition, const string &message) + { + tests_run++; + if (!condition) + cout << "❌ FAILED: " << message << endl; + else + tests_passed++; + } + + template + void assert_throws(Func func, const string &message) + { + tests_run++; + try + { + func(); + cout << "❌ FAILED (no exception): " << message << endl; + } + catch (const ExceptionType&) + { + tests_passed++; + } + catch (...) + { + cout << "❌ FAILED (wrong exception): " << message << endl; + } + } + + struct LifetimeTracker + { + static int live_count; + static int constructions; + static int destructions; + + int value; + + LifetimeTracker(int v = 0) : value(v) + { + constructions++; + live_count++; + } + + LifetimeTracker(const LifetimeTracker& other) + : value(other.value) + { + constructions++; + live_count++; + } + + ~LifetimeTracker() + { + destructions++; + live_count--; + } + }; + + int LifetimeTracker::live_count = 0; + int LifetimeTracker::constructions = 0; + int LifetimeTracker::destructions = 0; + + void reset_lifetime_counters() + { + LifetimeTracker::live_count = 0; + LifetimeTracker::constructions = 0; + LifetimeTracker::destructions = 0; + } + + + void test_basic_add_and_growth() + { + cout << "\nRunning test_basic_add_and_growth...\n"; + + dynamic_array arr; + + assert_true(arr.length() == 0, "Initial length should be 0"); + assert_true(arr.capacity() == 0, "Initial capacity should be 0"); + + arr.add(10); + assert_true(arr.capacity() == 1, "Capacity should grow to 1"); + + arr.add(20); + assert_true(arr.capacity() == 2, "Capacity should double to 2"); + + arr.add(30); + assert_true(arr.capacity() == 4, "Capacity should double to 4"); + + assert_true(arr.length() == 3, "Length should be 3"); + assert_true(arr[0] == 10, "Index 0 correct"); + assert_true(arr[2] == 30, "Index 2 correct"); + } + + void test_string() + { + cout << "\nRunning test_string...\n"; + + dynamic_array arr; + + arr.add("Hello"); + add(arr, "world"); + arr[1] += "!"; + get(arr, 1) += "!"; + + assert_true(arr[1] == "world!!", "Index 1 should equal 'world!!'"); + } + + void test_remove_and_shrink() + { + cout << "\nRunning test_remove_and_shrink...\n"; + + dynamic_array arr; + + for (int i = 0; i < 8; ++i) + arr.add(i); + + int initial_capacity = arr.capacity(); + + for (int i = 0; i < 6; ++i) + arr.remove(0); + + assert_true(arr.length() == 2, "Length should be 2 after removals"); + assert_true(arr.capacity() < initial_capacity, + "Capacity should shrink after enough removals"); + + arr.remove(0); + arr.remove(0); + + assert_true(arr.length() == 0, "Array should be empty"); + assert_true(arr.capacity() == 0, "Capacity should shrink to 0"); + } + + + void test_exceptions() + { + cout << "\nRunning test_exceptions...\n"; + + dynamic_array arr; + + assert_throws( + [&]() { arr.get(0); }, + "Accessing empty array should throw" + ); + + arr.add(5); + + assert_throws( + [&]() { arr.get(-1); }, + "Negative index should throw" + ); + + assert_throws( + [&]() { arr.get(5); }, + "Out of range index should throw" + ); + } + + + void test_accessors() + { + cout << "\nRunning test_accessors...\n"; + + dynamic_array arr; + + add(arr, 10); + add(arr, 20); + + assert_true(length(arr) == 2, "Free length works"); + assert_true(capacity(arr) >= 2, "Free capacity works"); + + const dynamic_array& const_arr = arr; + + assert_true(get(const_arr, 1) == 20, "Const get works"); + assert_true(const_arr[0] == 10, "Const operator[] works"); + } + + + void test_lifetime_management() + { + cout << "\nRunning test_lifetime_management...\n"; + + reset_lifetime_counters(); + + { + dynamic_array arr; + + for (int i = 0; i < 5; ++i) + arr.add(LifetimeTracker(i)); + + assert_true(LifetimeTracker::live_count == 5, + "Live objects should equal array size"); + + arr.remove(2); + assert_true(LifetimeTracker::live_count == 4, + "Removing should destroy one object"); + + arr.remove(0); + arr.remove(0); + arr.remove(0); + arr.remove(0); + + assert_true(LifetimeTracker::live_count == 0, + "All elements removed should destroy all objects"); + } + + assert_true(LifetimeTracker::live_count == 0, + "All objects destroyed after array destruction"); + + assert_true( + LifetimeTracker::constructions == LifetimeTracker::destructions, + "Construction and destruction counts should match" + ); + } + + void run_all_dynamic_tests() + { + test_basic_add_and_growth(); + test_string(); + test_remove_and_shrink(); + test_exceptions(); + test_accessors(); + test_lifetime_management(); + + cout << "\n=========================\n"; + cout << "Tests passed: " << tests_passed + << " / " << tests_run << endl; + cout << "=========================\n"; + } + +} + +void run_arrays_test() { + + bounded_tests::run_all_tests(); + dynamic_tests::run_all_dynamic_tests(); +} diff --git a/coresdk/src/test/test_main.cpp b/coresdk/src/test/test_main.cpp index e4b7750a..42320e5c 100644 --- a/coresdk/src/test/test_main.cpp +++ b/coresdk/src/test/test_main.cpp @@ -69,6 +69,7 @@ void setup_tests() add_test("GPIO - I2C HT16K33 LED matrix Tests", run_gpio_i2c_led_matrix_tests); add_test("GPIO - I2C HT16K33 LED 14 Segment Tests", run_gpio_i2c_quad_14_seg_test); add_test("Gen AI", run_genai_test); + add_test("Bounded/Dynamic Arrays", run_arrays_test); } int main(int argv, char **args) diff --git a/coresdk/src/test/test_main.h b/coresdk/src/test/test_main.h index 89f42267..855a9624 100644 --- a/coresdk/src/test/test_main.h +++ b/coresdk/src/test/test_main.h @@ -45,5 +45,6 @@ void run_terminal_test(); void run_logging_test(); void run_ui_test(); void run_genai_test(); +void run_arrays_test(); #endif /* test_main_h */ diff --git a/languages/cpp/splashkit-arrays.h b/languages/cpp/splashkit-arrays.h new file mode 100644 index 00000000..26ea3ead --- /dev/null +++ b/languages/cpp/splashkit-arrays.h @@ -0,0 +1,710 @@ +#ifndef splashkit_arrays_h +#define splashkit_arrays_h + +#if !(defined(__terminal_h) || defined(terminal_h)) + +#include "splashkit.h" + +#endif + +#include +#include + +/** + * Exception thrown when attempting to add an element to a + * bounded_array that has already reached its maximum capacity. + */ +struct array_full {}; + +/** + * Exception thrown when attempting to access or remove + * an element using an invalid index. + */ +struct array_invalid_index {}; + +/** + * Exception thrown when a memory allocation fails. + */ +struct array_allocation_failed {}; + + +/** + * A fixed-capacity array container. + * + * bounded_array stores up to MAX_CAPACITY elements of type T. + * Elements are stored contiguously and accessed by index. + * + * This container does not dynamically resize. Attempting to add + * more than MAX_CAPACITY elements will result in an array_full + * exception being thrown. + * + * Bounds checking is performed for element access and removal. + * Invalid index access results in an array_invalid_index exception. + * + * @tparam T The type of elements stored in the array + * @tparam MAX_CAPACITY The maximum number of elements the array can hold + */ +template +class bounded_array +{ + int size; + T data[MAX_CAPACITY]; + + void check_index(int index, const std::string& access_type) const + { + if (index < 0 || index >= size) + { + if (size == 0) + { + write_line("Cannot access index " + to_string(index) + + " because array is empty."); + } + else + { + write_line("Index to " + access_type + " (" + to_string(index) + ") is outside of range 0 - " + to_string(size - 1) + "."); + } + throw array_invalid_index(); + } + } + + public: + /** + * Constructs an empty bounded_array. + * + * The initial length of the array is 0. + */ + bounded_array() + { + size = 0; + } + + /** + * Returns the maximum number of elements this array can store. + * + * @return The maximum capacity of the array + */ + int capacity() const + { + return MAX_CAPACITY; + } + + /** + * Returns the current number of elements stored in the array. + * + * @return The number of valid elements in the array + */ + int length() const + { + return size; + } + + /** + * Returns a reference to the element at the specified index. + * + * @param index The index of the element to access + * + * @return A reference to the element at the given index + * + * @throws array_invalid_index if index is outside the range + * 0 to length() - 1 + */ + T& get(int index) + { + check_index(index, "access"); + return data[index]; + } + + /** + * Returns a const reference to the element at the specified index. + * + * This overload allows access on const bounded_array objects. + * + * @param index The index of the element to access + * + * @return A const reference to the element at the given index + * + * @throws array_invalid_index if index is outside the range + * 0 to length() - 1 + */ + const T& get(int index) const + { + check_index(index, "access"); + return data[index]; + } + + /** + * Returns a reference to the element at the specified index. + * + * @param index The index of the element to access + * + * @return A reference to the element at the given index + * + * @throws array_invalid_index if index is invalid + */ + T& operator[](int index) + { + return get(index); + } + + /** + * Returns a const reference to the element at the specified index. + * + * @param index The index of the element to access + * + * @return A const reference to the element at the given index + * + * @throws array_invalid_index if index is invalid + */ + const T& operator[](int index) const + { + return get(index); + } + + /** + * Adds a new element to the end of the array. + * + * The element is copied into the next available position. + * + * @param value The value to add to the array + * + * @throws array_full if the array has already reached MAX_CAPACITY + */ + void add(const T& value) + { + if (size >= MAX_CAPACITY) + { + write_line("Tried to add a new element when size has already reached maximum capacity (" + to_string(MAX_CAPACITY) + ")"); + throw array_full(); + } + data[size++] = value; + } + + /** + * Removes the element at the specified index. + * + * All elements after the removed element are shifted + * one position to the left. + * + * @param index The index of the element to remove + * + * @throws array_invalid_index if index is outside the valid range + */ + void remove(int index) + { + check_index(index, "remove"); + + for(int i = index; i < size - 1; i ++) + { + data[i] = data[i + 1]; + } + + size--; + } +}; + +/** + * Returns the maximum number of elements that the given + * bounded_array can store. + * + * @tparam T The type of elements stored in the array + * @tparam MAX_CAPACITY The maximum capacity of the array + * + * @param array The bounded_array to query + * + * @return The maximum capacity of the array + */ +template +int capacity(const bounded_array& array) +{ + return array.capacity(); +} + +/** + * Returns the current number of elements stored in the given + * bounded_array. + * + * @tparam T The type of elements stored in the array + * @tparam MAX_CAPACITY The maximum capacity of the array + * + * @param array The bounded_array to query + * + * @return The number of elements currently stored in the array + */ +template +int length(const bounded_array& array) +{ + return array.length(); +} + +/** + * Returns a reference to the element at the specified index + * within the given bounded_array. + * + * @tparam T The type of elements stored in the array + * @tparam MAX_CAPACITY The maximum capacity of the array + * + * @param array The bounded_array to access + * @param index The index of the element to retrieve + * + * @return A reference to the element at the given index + * + * @throws array_invalid_index if index is outside the valid range + */ +template +T& get(bounded_array& array, int index) +{ + return array.get(index); +} + +/** + * Returns a const reference to the element at the specified index + * within the given bounded_array. + * + * This overload allows access to elements of a const bounded_array. + * + * @tparam T The type of elements stored in the array + * @tparam MAX_CAPACITY The maximum capacity of the array + * + * @param array The bounded_array to access + * @param index The index of the element to retrieve + * + * @return A const reference to the element at the given index + * + * @throws array_invalid_index if index is outside the valid range + */ + +template +const T& get(const bounded_array& array, int index) +{ + return array.get(index); +} + +/** + * Adds a new element to the end of the given bounded_array. + * + * @tparam T The type of elements stored in the array + * @tparam MAX_CAPACITY The maximum capacity of the array + * @tparam U The type of element being added + * + * @param array The bounded_array to modify + * @param value The value to add to the array + * + * @throws array_full if the array has reached its maximum capacity + */ +template +void add(bounded_array& array, U&& value) +{ + array.add(std::forward(value)); +} + +/** + * Removes the element at the specified index from the given + * bounded_array. + * + * All elements following the removed element are shifted one + * position to the left. + * + * @tparam T The type of elements stored in the array + * @tparam MAX_CAPACITY The maximum capacity of the array + * + * @param array The bounded_array to modify + * @param index The index of the element to remove + * + * @throws array_invalid_index if index is outside the valid range + */ +template +void remove(bounded_array& array, int index) +{ + array.remove(index); +} + + + + +/** + * A dynamically resizing array container. + * + * dynamic_array stores elements of type T in contiguous memory. + * It automatically grows its internal storage as elements are added, + * doubling the capacity when full, and may shrink when elements are removed. + * + * Bounds checking is performed for element access and removal. + * Invalid index access results in an array_invalid_index exception. + * Memory allocation failures throw array_allocation_failed. + * + * This container cannot be assigned or copied. + * + * @tparam T The type of elements stored in the array + */ +template +class dynamic_array +{ + int size; + int current_capacity; + T* data; + + void check_index(int index, const std::string& access_type) const + { + if (index < 0 || index >= size) + { + if (size == 0) + { + write_line("Cannot access index " + to_string(index) + + " because array is empty."); + } + else + { + write_line("Index to " + access_type + " (" + to_string(index) + ") is outside of range 0 - " + to_string(size - 1) + "."); + } + throw array_invalid_index(); + } + } + + void resize(int new_capacity) + { + if (new_capacity == current_capacity) + { + return; + } + + if (new_capacity == 0) + { + // Destroy all live elements + for (int i = 0; i < size; i++) + { + data[i].~T(); + } + + std::free(data); + data = nullptr; + current_capacity = 0; + return; + } + + void* raw = std::malloc(sizeof(T) * new_capacity); + if (!raw) + { + throw array_allocation_failed(); + } + + T* new_data = static_cast(raw); + + // Copy construct existing elements into new storage + // But be able to undo if an exception is thrown during construction + int constructed = 0; + try + { + int elements_to_copy = (size < new_capacity) ? size : new_capacity; + + for (; constructed < size; constructed++) + { + new (&new_data[constructed]) T(data[constructed]); + } + + size = elements_to_copy; + } + catch (...) + { + // Destroy partially constructed elements + for (int i = 0; i < constructed; i++) + new_data[i].~T(); + + std::free(new_data); + throw; + } + + // Destroy old elements + for (int i = 0; i < size; ++i) + { + data[i].~T(); + } + + std::free(data); + + data = new_data; + current_capacity = new_capacity; + } + + public: + + /** + * Constructs an empty dynamic_array. + */ + dynamic_array() + : size(0), current_capacity(0), data(nullptr) + { + } + + /** + * Destructor. + * + * Destroys all valid elements and frees allocated memory. + */ + ~dynamic_array() + { + for (int i = 0; i < size; ++i) + { + data[i].~T(); + } + std::free(data); + } + + // Disable copy & assignment + dynamic_array(const dynamic_array&) = delete; + dynamic_array& operator=(const dynamic_array&) = delete; + + /** + * Returns the current capacity of the array. + * + * @return The number of elements that can be stored + * without resizing. + */ + int capacity() const + { + return current_capacity; + } + + /** + * Returns the current number of elements stored in the array. + * + * @return The number of valid elements in the array + */ + int length() const + { + return size; + } + + /** + * Returns a reference to the element at the specified index. + * + * @param index The index of the element to access + * + * @return A reference to the element at the given index + * + * @throws array_invalid_index if index is outside the range + * 0 to length() - 1 + */ + T& get(int index) + { + check_index(index, "access"); + return data[index]; + } + + /** + * Returns a const reference to the element at the specified index. + * + * This overload allows access on const bounded_array objects. + * + * @param index The index of the element to access + * + * @return A const reference to the element at the given index + * + * @throws array_invalid_index if index is outside the range + * 0 to length() - 1 + */ + const T& get(int index) const + { + check_index(index, "access"); + return data[index]; + } + + /** + * Returns a reference to the element at the specified index. + * + * @param index The index of the element to access + * + * @return A reference to the element at the given index + * + * @throws array_invalid_index if index is invalid + */ + T& operator[](int index) + { + return get(index); + } + + /** + * Returns a const reference to the element at the specified index. + * + * @param index The index of the element to access + * + * @return A const reference to the element at the given index + * + * @throws array_invalid_index if index is invalid + */ + const T& operator[](int index) const + { + return get(index); + } + + /** + * Adds a new element to the end of the array. + * + * The element is copied into the next available position. + * + * @param value The value to add to the array + * + * @throws array_allocation_failed if memory allocation fails. + */ + void add(const T& value) + { + if (size == current_capacity) + { + int new_capacity = (current_capacity == 0) ? 1 : current_capacity * 2; + resize(new_capacity); + } + + new (&data[size]) T(value); + size++; + } + + /** + * Removes the element at the specified index. + * + * All elements after the removed element are shifted + * one position to the left. + * + * @param index The index of the element to remove + * + * @throws array_invalid_index if index is outside the valid range + * @throws array_allocation_failed if shrinking allocation fails. + */ + void remove(int index) + { + check_index(index, "remove"); + + // Destroy the element at index + data[index].~T(); + + for(int i = index; i < size - 1; i ++) + { + new (&data[i]) T(data[i + 1]); + data[i + 1].~T(); + } + + size--; + + // Shrink if necessary + if (size > 0 && size < current_capacity / 2) + { + resize(current_capacity / 2); + } + else if (size == 0) + { + resize(0); + } + } +}; + +/** + * Returns the current capacity that the given + * dynamic_array can store without resizing. + * + * @tparam T The type of elements stored in the array + * + * @param array The dynamic_array to query + * + * @return The number of elements that can be stored + * without resizing. + */ +template +int capacity(const dynamic_array& array) +{ + return array.capacity(); +} + +/** + * Returns the current number of elements stored in the given + * dynamic_array. + * + * @tparam T The type of elements stored in the array + * + * @param array The dynamic_array to query + * + * @return The number of elements currently stored in the array + */ +template +int length(const dynamic_array& array) +{ + return array.length(); +} + +/** + * Returns a reference to the element at the specified index + * within the given dynamic_array. + * + * @tparam T The type of elements stored in the array + * + * @param array The dynamic_array to access + * @param index The index of the element to retrieve + * + * @return A reference to the element at the given index + * + * @throws array_invalid_index if index is outside the valid range + */ +template +T& get(dynamic_array& array, int index) +{ + return array.get(index); +} + +/** + * Returns a const reference to the element at the specified index + * within the given dynamic_array. + * + * This overload allows access to elements of a const dynamic_array. + * + * @tparam T The type of elements stored in the array + * + * @param array The dynamic_array to access + * @param index The index of the element to retrieve + * + * @return A const reference to the element at the given index + * + * @throws array_invalid_index if index is outside the valid range + */ + +template +const T& get(const dynamic_array& array, int index) +{ + return array.get(index); +} + +/** + * Adds a new element to the end of the given dynamic_array. + * + * @tparam T The type of elements stored in the array + * @tparam U The type of element being added + * + * @param array The dynamic_array to modify + * @param value The value to add to the array + * + * @throws array_allocation_failed if memory allocation fails. + */ +template +void add(dynamic_array& array, U&& value) +{ + array.add(std::forward(value)); +} + +/** + * Removes the element at the specified index from the given + * dynamic_array. + * + * All elements following the removed element are shifted one + * position to the left. + * + * @tparam T The type of elements stored in the array + * + * @param array The dynamic_array to modify + * @param index The index of the element to remove + * + * @throws array_invalid_index if index is outside the valid range + * @throws array_allocation_failed if shrinking allocation fails. + */ +template +void remove(dynamic_array& array, int index) +{ + array.remove(index); +} + +#endif diff --git a/projects/cmake/CMakeLists.txt b/projects/cmake/CMakeLists.txt index 894e89e6..e46c17fd 100644 --- a/projects/cmake/CMakeLists.txt +++ b/projects/cmake/CMakeLists.txt @@ -12,6 +12,7 @@ set(SK_EXT "${CMAKE_CURRENT_SOURCE_DIR}/../../coresdk/external") set(SK_LIB "${CMAKE_CURRENT_SOURCE_DIR}/../../coresdk/lib") set(SK_OUT "${CMAKE_CURRENT_SOURCE_DIR}/../../out") set(SK_BIN "${CMAKE_CURRENT_SOURCE_DIR}/../../bin") +set(SK_CPP "${CMAKE_CURRENT_SOURCE_DIR}/../../languages/cpp") # FLAGS set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20") @@ -247,6 +248,7 @@ include_directories("${SK_EXT}/catch") include_directories("${SK_EXT}/microui/src") include_directories("${SK_EXT}/llama.cpp/include") include_directories("${SK_EXT}/llama.cpp/ggml/include") +include_directories("${SK_CPP}") # MAC OS DIRECTORY INCLUDES if (APPLE) diff --git a/tools/scripts/cmake/libsplashkit/CMakeLists.txt b/tools/scripts/cmake/libsplashkit/CMakeLists.txt index 1b0809b9..022ef360 100644 --- a/tools/scripts/cmake/libsplashkit/CMakeLists.txt +++ b/tools/scripts/cmake/libsplashkit/CMakeLists.txt @@ -158,6 +158,12 @@ file(GLOB SK_LIB_INCLUDE_FILE "${SK_GENERATED}/clib/*.h" ) +# C++ specific includes + +file(GLOB CPP_ONLY_INCLUDE_FILES + "${SK_ROOT}/languages/cpp/splashkit-arrays.h" +) + # DIRECTORY INCLUDES include_directories("${SK_SRC}") include_directories("${SK_SRC}/coresdk") @@ -266,7 +272,7 @@ add_definitions(-DELPP_THREAD_SAFE) #### SplashKitBackend STATIC LIBRARY #### -add_library(SplashKit SHARED ${SOURCE_FILES} ${OS_SOURCE_FILES} ${INCLUDE_FILES} ${ADAPTER_INCLUDE_FILES} ${ADAPTER_CPP_FILES}) +add_library(SplashKit SHARED ${SOURCE_FILES} ${OS_SOURCE_FILES} ${INCLUDE_FILES} ${ADAPTER_INCLUDE_FILES} ${CPP_ONLY_INCLUDE_FILES} ${ADAPTER_CPP_FILES}) target_link_libraries(SplashKit ${LIB_FLAGS} ${LLAMA_LIB_FLAGS}) if (LINUX) @@ -307,8 +313,10 @@ file(GLOB INL_FILES # Adapter files install(FILES ${ADAPTER_INCLUDE_FILES} DESTINATION ${SK_DEPLOY_ROOT}/g++/include) +install(FILES ${CPP_ONLY_INCLUDE_FILES} DESTINATION ${SK_DEPLOY_ROOT}/g++/include) install(FILES ${ADAPTER_CPP_FILES} DESTINATION ${SK_DEPLOY_ROOT}/g++/src) install(FILES ${ADAPTER_INCLUDE_FILES} DESTINATION ${SK_DEPLOY_ROOT}/clang++/include) +install(FILES ${CPP_ONLY_INCLUDE_FILES} DESTINATION ${SK_DEPLOY_ROOT}/clang++/src) install(FILES ${ADAPTER_CPP_FILES} DESTINATION ${SK_DEPLOY_ROOT}/clang++/src) install(FILES ${LINUX_INCLUDE_FILES} DESTINATION ${SK_DEPLOY_ROOT}/source/include) From aef15ae4fc204da4236fe2cbe46c86f6ce99c352 Mon Sep 17 00:00:00 2001 From: Sean Boettger Date: Thu, 19 Feb 2026 18:50:00 +1100 Subject: [PATCH 2/2] Fix: make libllama.a position-independent so it links correctly --- tools/scripts/cmake/libsplashkit/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/scripts/cmake/libsplashkit/CMakeLists.txt b/tools/scripts/cmake/libsplashkit/CMakeLists.txt index 022ef360..8a31f5c7 100644 --- a/tools/scripts/cmake/libsplashkit/CMakeLists.txt +++ b/tools/scripts/cmake/libsplashkit/CMakeLists.txt @@ -238,6 +238,7 @@ else() -DLLAMA_BUILD_COMMON=OFF -DLLAMA_TOOLS_INSTALL=OFF -DCMAKE_BUILD_TYPE=Release + -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DGGML_STATIC=ON -DGGML_OPENMP=OFF -DCMAKE_INSTALL_PREFIX=