diff --git a/source/coral/sequence.cpp b/source/coral/sequence.cpp deleted file mode 100644 index f73a83d..0000000 --- a/source/coral/sequence.cpp +++ /dev/null @@ -1,240 +0,0 @@ -export module coral.sequence; - -import coral; - -// Collections. -export namespace coral { - /** - * Result codes used by [contiguous_range]-derived types when they are appended to in any way. - * - * [append_result::ok] indicates that an append operation was successful. - * - * [append_result::out_of_memory] alerts that the memory required to perform the append - * operation failed. - */ - enum class [[nodiscard]] append_result { - ok, - out_of_memory, - }; - - /** - * Base type for all collections which store their elements as a single block of contiguous - * memory. - * - * Sequences are any data structure which owns a linear, non-unique set of elements which may - * be queried and/or mutated. - */ - template struct contiguous_range { - virtual ~contiguous_range() {}; - - /** - * Attempts to append `source_elements` to the contiguous_range. - * - * The returned [append_result] indicates whether the operation was successful or not. - * - * If the returned [append_result] is anything but [append_result::ok], the - * [contiguous_range] will be left in an implementation-defined state. - */ - virtual append_result append(slice const & source_elements) = 0; - - /** - * Returns a read-only [slice] of the current range values. - * - * *Note*: the behavior of retaining the returned value past the scope of the source - * [contiguous_range] or any subsequent modifications to it is implementation-defined. - */ - virtual slice as_slice() = 0; - }; - - /** - * Last-in-first-out contiguous range of `element` values. - * - * [stack] types will default to using an inline array of `init_capacity` at first. After all - * local storage has been exhausted, the [stack] will switch to a dynamic buffer. Because of - * this, it is recommended to use larger `init_capacity` values for data which has a known or - * approximate upper bound at compile-time. Otherwise, the `init_capacity` value may be left at - * its default. - * - * *Note*: the [allocator] referenced in the stack must remain valid for the duration of the - * stack lifetime. - */ - template struct stack : public contiguous_range { - stack(allocator * dynamic_allocator) { - this->dynamic_allocator = dynamic_allocator; - } - - ~stack() override { - if (this->is_dynamic()) { - for (element & e : this->elements) e.~element(); - - this->dynamic_allocator->deallocate(this->elements.pointer); - } - } - - /** - * Attempts to append `source_elements` to the top of the stack. - * - * The returned [append_result] indicates whether the operation was successful or not. - * - * If the returned [append_result] is anything but [append_result::ok], the stack will be - * be left in an empty but valid state. - * - * *Note* that [push] is recommended when appending singular values. - */ - append_result append(slice const & source_elements) override { - usize const updated_fill = this->filled + source_elements.length; - - if (updated_fill >= this->elements.length) { - append_result const result = this->reserve(updated_fill); - - if (result != append_result::ok) return result; - } - - for (usize i = 0; i < source_elements.length; i += 1) - this->elements[this->filled + i] = source_elements[i]; - - this->filled = updated_fill; - - return append_result::ok; - } - - /** - * Returns a read-only [slice] of the current stack values. - * - * *Note*: the returned slice should be considered invalid if any mutable operation is - * performed on the source [stack] or it is no longer in scope. - */ - slice as_slice() override { - return this->elements.sliced(0, this->filled); - } - - /** - * Returns `true` if the stack is backed by dynamic memory, otherwise `false`. - */ - bool is_dynamic() const { - return this->elements.pointer != reinterpret_cast(this->local_buffer); - } - - /** - * Attempts to append `source_element` to the top of the stack. - * - * The returned [append_result] indicates whether the operation was successful or not. - * - * If the returned [append_result] is anything but [append_result::ok], the stack will be - * be left in an empty but valid state. - * - * *Note* that [append] is recommended when appending many values at once. - */ - append_result push(element const & source_element) { - if (this->filled == this->elements.length) { - append_result const result = this->reserve(this->elements.length); - - if (result != append_result::ok) return result; - } - - this->elements[this->filled] = source_element; - this->filled += 1; - - return append_result::ok; - } - - /** - * Attempts to reserve `capacity` number of elements additional space on the stack, forcing - * it to use dynamic memory _even_ if it hasn't exhausted the local buffer yet. - * - * The returned [append_result] indicates whether the operation was successful or not. - * - * If the returned [append_result] is anything but [append_result::ok], the stack will be - * be left in an empty but valid state. - * - * *Note* that manual invocation is not recommended if the [stack] has a large - * `initial_capacity` argument. - */ - append_result reserve(usize capacity) { - usize const requested_capacity = this->filled + capacity; - - if (this->is_dynamic()) { - u8 * const buffer = this->dynamic_allocator->reallocate( - reinterpret_cast(this->elements.pointer), - sizeof(element) * requested_capacity); - - if (buffer == nullptr) { - this->elements = {}; - - return append_result::out_of_memory; - } - - this->elements = {reinterpret_cast(buffer), requested_capacity}; - } else { - usize const buffer_size = sizeof(element) * requested_capacity; - u8 * const buffer = this->dynamic_allocator->reallocate(nullptr, buffer_size); - - if (buffer == nullptr) { - this->elements = {}; - - return append_result::out_of_memory; - } - - copy({buffer, buffer_size}, this->elements.as_bytes()); - - this->elements = {reinterpret_cast(buffer), requested_capacity}; - } - - return append_result::ok; - } - - private: - allocator * dynamic_allocator{nullptr}; - - usize filled{0}; - - slice elements{reinterpret_cast(local_buffer), init_capacity}; - - u8 local_buffer[init_capacity]{0}; - }; -} - -using contiguous_byte_range = coral::contiguous_range; - -// Reader / writers. -export namespace coral { - /** - * Readable type for streaming data from a [contiguous_range] containing [u8] values. - */ - struct contiguous_reader : public reader { - contiguous_reader(contiguous_byte_range * range) { - this->range = range; - } - - expected write(slice const & buffer) { - switch (this->range->append(buffer)) { - case append_result::ok: return buffer.length; - case append_result::out_of_memory: return io_error::unavailable; - default: unreachable(); - } - } - - private: - contiguous_byte_range * range; - }; - - /** - * Writable type for appending data to a [contiguous_range] containing [u8] values. - */ - struct contiguous_writer : public writer { - contiguous_writer(contiguous_byte_range * range) { - this->range = range; - } - - expected write(slice const & buffer) { - switch (this->range->append(buffer)) { - case append_result::ok: return buffer.length; - case append_result::out_of_memory: return io_error::unavailable; - default: unreachable(); - } - } - - private: - contiguous_byte_range * range; - }; -} diff --git a/source/coral/stack.cpp b/source/coral/stack.cpp new file mode 100644 index 0000000..50b74dd --- /dev/null +++ b/source/coral/stack.cpp @@ -0,0 +1,253 @@ +export module coral.stack; + +import coral; + +// Collections. +export namespace coral { + /** + * Result codes used by [contiguous_range]-derived types when they are appended to in any way. + * + * [push_result::ok] indicates that an push operation was successful. + * + * [push_result::out_of_memory] alerts that the memory required to perform the push operation + * failed. + */ + enum class [[nodiscard]] push_result { + ok, + out_of_memory, + }; + + /** + * Base type for all stack types. + * + * Sequences are any data structure which owns a linear, non-unique set of elements which may + * be queried and/or mutated. + */ + template struct stack { + virtual ~stack() {}; + + /** + * Returns a read-only [slice] of the current range values. + * + * *Note*: the behavior of retaining the returned value past the scope of the source + * [stack] or any subsequent modifications to it is implementation-defined. + */ + virtual slice as_slice() const = 0; + + /** + * Attempts to append `source_elements` to the stack. + * + * The returned [push_result] indicates whether the operation was successful or not. + * + * If the returned [push_result] is anything but [push_result::ok], the [stack] will be + * left in an implementation-defined state. + */ + virtual push_result push_all(slice const & source_elements) = 0; + }; + + /** + * Last-in-first-out contiguous sequence of `element` values optimized for small numbers of + * small-sized elements. + * + * [small_stack] types will default to using an inline array of `init_capacity` at first. After + * all local storage has been exhausted, the [small_stack] will switch to a dynamic buffer. + * Because of this, it is recommended to use larger `init_capacity` values for data which has a + * known or approximate upper bound at compile-time. Otherwise, the `init_capacity` value may + * be left at its default. + * + * *Note*: the [allocator] referenced in the stack must remain valid for the duration of the + * stack lifetime. + */ + template struct small_stack : public stack { + small_stack(allocator * dynamic_allocator) { + this->dynamic_allocator = dynamic_allocator; + } + + ~small_stack() override { + if (this->is_dynamic()) { + for (element & e : this->elements) e.~element(); + + this->dynamic_allocator->deallocate(this->elements.pointer); + } + } + + /** + * Returns a read-only [slice] of the current stack values. + * + * *Note*: the returned slice should be considered invalid if any mutable operation is + * performed on the source [stack] or it is no longer in scope. + */ + slice as_slice() const override { + return this->elements.sliced(0, this->filled); + } + + /** + * Returns `true` if the stack is backed by dynamic memory, otherwise `false`. + */ + bool is_dynamic() const { + return this->elements.pointer != reinterpret_cast(this->local_buffer); + } + + /** + * Attempts to append `source_element` to the top of the stack. + * + * The returned [push_result] indicates whether the operation was successful or not. + * + * If the returned [push_result] is anything but [push_result::ok], the stack will be + * be left in an empty but valid state. + * + * *Note* that [push_all] is recommended when appending many values at once. + */ + push_result push(element const & source_element) { + if (this->filled == this->elements.length) { + push_result const result = this->reserve(this->elements.length); + + if (result != push_result::ok) return result; + } + + this->elements[this->filled] = source_element; + this->filled += 1; + + return push_result::ok; + } + + /** + * Attempts to append `source_elements` to the top of the stack. + * + * The returned [push_result] indicates whether the operation was successful or not. + * + * If the returned [push_result] is anything but [push_result::ok], the stack will be left + * in an empty but valid state. + * + * *Note* that [push] is recommended when appending singular values. + */ + push_result push_all(slice const & source_elements) override { + usize const updated_fill = this->filled + source_elements.length; + + if (updated_fill >= this->elements.length) { + push_result const result = this->reserve(updated_fill); + + if (result != push_result::ok) return result; + } + + for (usize i = 0; i < source_elements.length; i += 1) + this->elements[this->filled + i] = source_elements[i]; + + this->filled = updated_fill; + + return push_result::ok; + } + + /** + * Attempts to reserve `capacity` number of elements additional space on the stack, forcing + * it to use dynamic memory _even_ if it hasn't exhausted the local buffer yet. + * + * The returned [push_result] indicates whether the operation was successful or not. + * + * If the returned [push_result] is anything but [push_result::ok], the stack will be left + * in an empty but valid state. + * + * *Note* that manual invocation is not recommended if the [stack] has a large + * `initial_capacity` argument. + */ + push_result reserve(usize capacity) { + usize const requested_capacity = this->filled + capacity; + + if (this->is_dynamic()) { + u8 * const buffer = this->dynamic_allocator->reallocate( + reinterpret_cast(this->elements.pointer), + sizeof(element) * requested_capacity); + + if (buffer == nullptr) { + this->elements = {}; + + return push_result::out_of_memory; + } + + this->elements = {reinterpret_cast(buffer), requested_capacity}; + } else { + usize const buffer_size = sizeof(element) * requested_capacity; + u8 * const buffer = this->dynamic_allocator->reallocate(nullptr, buffer_size); + + if (buffer == nullptr) { + this->elements = {}; + + return push_result::out_of_memory; + } + + copy({buffer, buffer_size}, this->elements.as_bytes()); + + this->elements = {reinterpret_cast(buffer), requested_capacity}; + } + + return push_result::ok; + } + + private: + allocator * dynamic_allocator{nullptr}; + + usize filled{0}; + + slice elements{reinterpret_cast(local_buffer), init_capacity}; + + u8 local_buffer[init_capacity]{0}; + }; +} + +using byte_stack = coral::stack; + +// Reader / writers. +export namespace coral { + /** + * Readable type for streaming data from a [stack] containing [u8] values. + */ + struct stack_reader : public reader { + stack_reader(byte_stack const * stack) { + this->cursor = 0; + this->stack = stack; + } + + /** + * Reads the data from the target stack into `buffer`, returning the number bytes read. + */ + expected read(slice const & buffer) override { + slice const stack_elements {this->stack->as_slice()}; + usize const read {min(buffer.length, stack_elements.length - this->cursor)}; + + copy(buffer, stack_elements.sliced(cursor, read)); + + this->cursor += read; + + return read; + } + + private: + usize cursor {0}; + + byte_stack const * stack {nullptr}; + }; + + /** + * Writable type for appending data to a [contiguous_range] containing [u8] values. + */ + struct stack_writer : public writer { + stack_writer(byte_stack * stack) { + this->stack = stack; + } + + /** + * Attempts to write `buffer` to the target stack, returning the number of bytes written or + * an [io_error] if it failed to commit `buffer` to the stack memory. + */ + expected write(slice const & buffer) override { + switch (this->stack->push_all(buffer)) { + case push_result::ok: return buffer.length; + case push_result::out_of_memory: return io_error::unavailable; + default: unreachable(); + } + } + + private: + byte_stack * stack {nullptr}; + }; +} diff --git a/source/runtime.cpp b/source/runtime.cpp index b7b98aa..9beb574 100644 --- a/source/runtime.cpp +++ b/source/runtime.cpp @@ -6,7 +6,7 @@ import coral; import coral.files; import coral.io; import coral.math; -import coral.sequence; +import coral.stack; extern "C" int main(int argc, char const * const * argv) { return app::client::run("Ona Runtime", [](app::client & client) -> int { @@ -16,10 +16,10 @@ extern "C" int main(int argc, char const * const * argv) { client.resources().read_file(config_path, [&](coral::reader & file) { coral::allocator * const allocator{&client.thread_safe_allocator()}; - coral::stack script_source{allocator}; + coral::small_stack script_source{allocator}; { coral::u8 stream_buffer[1024]{0}; - coral::contiguous_writer script_writer{&script_source}; + coral::stack_writer script_writer{&script_source}; if (!coral::stream(script_writer, file, stream_buffer).is_ok()) return; } @@ -30,9 +30,9 @@ extern "C" int main(int argc, char const * const * argv) { }); if (!is_config_loaded) { - coral::stack error_message{&client.thread_safe_allocator()}; + coral::small_stack error_message{&client.thread_safe_allocator()}; { - coral::contiguous_writer error_writer{&error_message}; + coral::stack_writer error_writer{&error_message}; if (!error_writer.write(coral::slice{"failed to load "}.as_bytes()).is_ok()) return coral::u8_max;