From 468ff1a51020d0deb51472a61398e473e4531727 Mon Sep 17 00:00:00 2001 From: kayomn Date: Fri, 3 Mar 2023 17:08:49 +0000 Subject: [PATCH] Reduce complexity of coral::stack --- source/coral/stack.cpp | 263 +++++++---------------------------------- 1 file changed, 41 insertions(+), 222 deletions(-) diff --git a/source/coral/stack.cpp b/source/coral/stack.cpp index 95cba0f..c45a232 100755 --- a/source/coral/stack.cpp +++ b/source/coral/stack.cpp @@ -5,9 +5,9 @@ import coral; // Collections. export namespace coral { /** - * Result codes used by [contiguous_range]-derived types when they are appended to in any way. + * Result codes used when pushing elements to a stack. * - * [push_result::ok] indicates that an push operation was successful. + * [push_result::ok] indicates that the push operation was successful. * * [push_result::out_of_memory] alerts that the memory required to perform the push operation failed. */ @@ -17,175 +17,72 @@ export namespace coral { }; /** - * Base type for all stack types. + * Last-in-first-out contiguous sequence of `element` values. * - * Sequences are any data structure which owns a linear, non-unique set of elements which may be queried and/or - * mutated. + * *Note*: the [allocator] used by the stack must remain valid for the duration of the stack lifetime. */ template struct stack { - virtual ~stack() {}; - - /** - * Returns `true` if there are no elements in the stack, otherwise `false`. - */ - virtual bool is_empty() const = 0; - - /** - * Invokes `apply` on each element in the stack and evaluating a user-defined condition which will return a - * `bool` at the end of each evaluation. After all elements have been evaluated, `true` is returned if every - * element elavuated `true`. Otherwise, `false` is returned to indicate that one of more elements failed - * evaluation. - * - * *Note*: This function uses short-circuit evaluation, so the enumeration will terminate upon the first failure - * case. This may be leveraged to create conditional looping behavior. - */ - virtual bool every(closure apply) = 0; - - /** - * Invokes `apply` on each element in the stack and evaluating a user-defined condition which will return a - * `bool` at the end of each evaluation. After all elements have been evaluated, `true` is returned if every - * element elavuated `true`. Otherwise, `false` is returned to indicate that one of more elements failed - * evaluation. - * - * *Note*: This function uses short-circuit evaluation, so the enumeration will terminate upon the first failure - * case. This may be leveraged to create conditional looping behavior. - */ - virtual bool every(closure apply) 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 final : public stack { - small_stack(allocator & dynamic_allocator) : 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); - } + stack(allocator & element_allocator) : element_allocator{element_allocator} { + this->filled_elements = {}; } - /** - * Returns a const [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 { - return this->elements.sliced(0, this->filled); - } + ~stack() { + for (element & e : this->filled_elements) e.~element(); - /** - * Invokes `apply` on each element in the stack and evaluating a user-defined condition which will return a - * `bool` at the end of each evaluation. After all elements have been evaluated, `true` is returned if every - * element elavuated `true`. Otherwise, `false` is returned to indicate that one of more elements failed - * evaluation. - * - * *Note*: This function uses short-circuit evaluation, so the enumeration will terminate upon the first failure - * case. This may be leveraged to create conditional looping behavior. - */ - bool every(closure apply) override { - for (usize index = 0; index < this->filled; index += 1) { - if (!apply(this->elements[index])) return false; - } - - return true; - } - - /** - * Invokes `apply` on each element in the stack and evaluating a user-defined condition which will return a - * `bool` at the end of each evaluation. After all elements have been evaluated, `true` is returned if every - * element elavuated `true`. Otherwise, `false` is returned to indicate that one of more elements failed - * evaluation. - * - * *Note*: This function uses short-circuit evaluation, so the enumeration will terminate upon the first failure - * case. This may be leveraged to create conditional looping behavior. - */ - bool every(closure apply) const override { - for (usize index = 0; index < this->filled; index += 1) { - if (!apply(this->elements[index])) return false; - } - - return true; - } - - /** - * 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); + this->element_allocator.deallocate(this->filled_elements.pointer); } /** * Returns `true` if there are no elements in the stack, otherwise `false`. */ - bool is_empty() const override { - return this->filled == 0; + bool is_empty() const { + return this->filled_elements.length == 0; } /** - * Attempts to append `source_element` to the top of the stack. + * Attempts to append `value` 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. + * *Note* [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); + push_result push(element const & value) { + if (this->filled_elements.length == this->element_capacity) { + constexpr usize min_size {2}; + push_result const result {this->reserve(this->element_capacity + (static_cast(this->element_capacity == 0) * min_size))}; if (result != push_result::ok) return result; } - this->elements[this->filled] = source_element; - this->filled += 1; + this->filled_elements[this->filled_elements.length] = value; + this->filled_elements.length += 1; return push_result::ok; } /** - * Attempts to append `source_elements` to the top of the stack. + * Attempts to append `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. + * *Note* [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; + push_result push_all(slice const & values) { + usize const updated_fill {this->filled + values.length}; if (updated_fill >= this->elements.length) { - push_result const result = this->reserve(updated_fill); + push_result const result {this->reserve(values.length)}; - if (result != push_result::ok) return result; + 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]; + for (usize i {0}; i < values.length; i += 1) this->elements[this->filled + i] = values[i]; this->filled = updated_fill; @@ -193,115 +90,37 @@ export namespace coral { } /** - * 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. + * Attempts to reserve `requested_capacity` number of elements additional space on 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 manual invocation is not recommended if the [stack] has a large `init_capacity` argument. */ - push_result reserve(usize capacity) { - usize const requested_capacity = this->filled + capacity; + push_result reserve(usize requested_capacity) { + usize const total_capacity {this->elements_filled + requested_capacity}; - if (this->is_dynamic()) { - u8 * const buffer = this->dynamic_allocator.reallocate( - reinterpret_cast(this->elements.pointer), - sizeof(element) * requested_capacity); + u8 * const buffer {this->element_allocator.reallocate(reinterpret_cast( + this->elements_filled.pointer), sizeof(element) * total_capacity)}; - if (buffer == nullptr) { - this->elements = {}; + if (buffer == nullptr) { + this->element_capacity = 0; + this->filled_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::out_of_memory; } + this->filled_elements.pointer = reinterpret_cast(buffer); + this->element_capacity = total_capacity; + return push_result::ok; } private: - allocator & dynamic_allocator; + allocator & element_allocator; - usize filled{0}; + usize element_capacity{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) : stack{stack} { - this->cursor = 0; - } - - /** - * Reads the data from the target stack into `buffer`, returning the number bytes read. - */ - expected read(slice const & buffer) override { - usize data_written = 0; - - this->stack.every([&](u8 byte) -> bool { - buffer[data_written] = byte; - data_written += 1; - - return data_written < buffer.length; - }); - - this->cursor += data_written; - - return data_written; - } - - private: - usize cursor {0}; - - byte_stack const & stack; - }; - - /** - * Writable type for appending data to a [contiguous_range] containing [u8] values. - */ - struct stack_writer : public writer { - stack_writer(byte_stack & stack) : 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; + slice filled_elements; }; }