Tidy up stack API in coral library
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
388a98e654
commit
0bfd1c80ce
|
@ -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<typename element> 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<element const> 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<element const> 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<typename element, usize init_capacity = 1> struct stack : public contiguous_range<element> {
|
|
||||||
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<element const> 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<element const> 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<element const *>(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<u8 *>(this->elements.pointer),
|
|
||||||
sizeof(element) * requested_capacity);
|
|
||||||
|
|
||||||
if (buffer == nullptr) {
|
|
||||||
this->elements = {};
|
|
||||||
|
|
||||||
return append_result::out_of_memory;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->elements = {reinterpret_cast<element *>(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<element *>(buffer), requested_capacity};
|
|
||||||
}
|
|
||||||
|
|
||||||
return append_result::ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
allocator * dynamic_allocator{nullptr};
|
|
||||||
|
|
||||||
usize filled{0};
|
|
||||||
|
|
||||||
slice<element> elements{reinterpret_cast<element *>(local_buffer), init_capacity};
|
|
||||||
|
|
||||||
u8 local_buffer[init_capacity]{0};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
using contiguous_byte_range = coral::contiguous_range<coral::u8>;
|
|
||||||
|
|
||||||
// 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<usize, io_error> write(slice<u8 const> 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<usize, io_error> write(slice<u8 const> 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;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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<typename element> 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<element const> 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<element const> 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<typename element, usize init_capacity = 1> struct small_stack : public stack<element> {
|
||||||
|
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<element const> 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<element const *>(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<element const> 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<u8 *>(this->elements.pointer),
|
||||||
|
sizeof(element) * requested_capacity);
|
||||||
|
|
||||||
|
if (buffer == nullptr) {
|
||||||
|
this->elements = {};
|
||||||
|
|
||||||
|
return push_result::out_of_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->elements = {reinterpret_cast<element *>(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<element *>(buffer), requested_capacity};
|
||||||
|
}
|
||||||
|
|
||||||
|
return push_result::ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
allocator * dynamic_allocator{nullptr};
|
||||||
|
|
||||||
|
usize filled{0};
|
||||||
|
|
||||||
|
slice<element> elements{reinterpret_cast<element *>(local_buffer), init_capacity};
|
||||||
|
|
||||||
|
u8 local_buffer[init_capacity]{0};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
using byte_stack = coral::stack<coral::u8>;
|
||||||
|
|
||||||
|
// 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<usize, io_error> read(slice<u8> 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<usize, io_error> write(slice<u8 const> 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};
|
||||||
|
};
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import coral;
|
||||||
import coral.files;
|
import coral.files;
|
||||||
import coral.io;
|
import coral.io;
|
||||||
import coral.math;
|
import coral.math;
|
||||||
import coral.sequence;
|
import coral.stack;
|
||||||
|
|
||||||
extern "C" int main(int argc, char const * const * argv) {
|
extern "C" int main(int argc, char const * const * argv) {
|
||||||
return app::client::run("Ona Runtime", [](app::client & client) -> int {
|
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) {
|
client.resources().read_file(config_path, [&](coral::reader & file) {
|
||||||
coral::allocator * const allocator{&client.thread_safe_allocator()};
|
coral::allocator * const allocator{&client.thread_safe_allocator()};
|
||||||
|
|
||||||
coral::stack<coral::u8> script_source{allocator};
|
coral::small_stack<coral::u8> script_source{allocator};
|
||||||
{
|
{
|
||||||
coral::u8 stream_buffer[1024]{0};
|
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;
|
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) {
|
if (!is_config_loaded) {
|
||||||
coral::stack<coral::u8> error_message{&client.thread_safe_allocator()};
|
coral::small_stack<coral::u8> 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())
|
if (!error_writer.write(coral::slice{"failed to load "}.as_bytes()).is_ok())
|
||||||
return coral::u8_max;
|
return coral::u8_max;
|
||||||
|
|
Loading…
Reference in New Issue