diff --git a/source/app.cpp b/source/app.cpp index 5097886..fc82c67 100644 --- a/source/app.cpp +++ b/source/app.cpp @@ -7,6 +7,7 @@ export module app; import coral; import coral.files; import coral.image; +import coral.io; import coral.math; import oar; diff --git a/source/coral/io.cpp b/source/coral/io.cpp new file mode 100644 index 0000000..07e1bd8 --- /dev/null +++ b/source/coral/io.cpp @@ -0,0 +1,175 @@ +export module coral.io; + +import coral; + +export namespace coral { + /** + * Multiplexing byte-based ring buffer of `capacity` size that may be used for memory-backed + * I/O operations and lightweight data construction. + */ + template struct fixed_buffer : public writer, public reader { + fixed_buffer(coral::u8 fill_value) : data{fill_value} {} + + /** + * Returns the base pointer of the buffer data. + */ + u8 * begin() { + return this->data; + } + + /** + * Returns the base pointer of the buffer data. + */ + u8 const * begin() const { + return this->data; + } + + /** + * Returns the tail pointer of the buffer data. + */ + u8 * end() { + return this->data + this->cursor; + } + + /** + * Returns the tail pointer of the buffer data. + */ + u8 const * end() const { + return this->data + this->cursor; + } + + /** + * Returns `true` if the buffer has been completely filled with data. + */ + bool is_full() const { + return this->filled == capacity; + } + + /** + * Reads whatever data is in the buffer into `data`, returning the number of bytes read + * from the buffer. + */ + expected read(slice const & data) override { + slice const readable_data{this->data, min(this->filled, data.length)}; + + this->filled -= readable_data.length; + + for (usize index = 0; index < readable_data.length; index += 1) { + data[index] = this->data[this->read_index]; + this->read_index = (this->read_index + 1) % capacity; + } + + return readable_data.length; + } + + /** + * Attempts to write `data` to the buffer, returning the number of bytes written or + * [io_error::unavailable] if it has been completely filled and no more bytes can be + * written. + */ + expected write(slice const & data) override { + if (this->is_full()) return io_error::unavailable; + + slice const writable_data{data.sliced(0, min(data.length, this->filled))}; + + this->filled += writable_data.length; + + for (usize index = 0; index < writable_data.length; index += 1) { + this->data[this->write_index] = data[index]; + this->write_index = (this->write_index + 1) % capacity; + } + + return writable_data.length; + } + + private: + usize filled = 0; + + usize read_index = 0; + + usize write_index = 0; + + u8 data[capacity]; + }; + + /** + * Streams the data from `input` to `output`, using `buffer` as temporary transfer space. + * + * The returned [expected] can be used to introspect if `input` or `output` encountered any + * issues during streaming, otherwise it will contain the number of bytes streamed. + * + * *Note*: if `buffer` has a length of `0`, no data will be streamed as there is nowhere to + * temporarily place data during streaming. + */ + expected stream(writer & output, reader & input, slice const & buffer) { + usize total_bytes_written = 0; + expected bytes_read = input.read(buffer); + + if (!bytes_read.is_ok()) return bytes_read.error(); + + usize read = bytes_read.value(); + + while (read != 0) { + expected const bytes_written = output.write(buffer.sliced(0, read)); + + if (!bytes_written.is_ok()) return bytes_read.error(); + + total_bytes_written += bytes_written.value(); + bytes_read = input.read(buffer); + + if (!bytes_read.is_ok()) return bytes_read.error(); + + read = bytes_read.value(); + } + + return total_bytes_written; + } + + /** + * Returns a reference to a shared [allocator] which will always return `nullptr` on calls to + * [allocator::reallocate]. + */ + allocator & null_allocator() { + static struct : public allocator { + u8 * reallocate(u8 * maybe_allocation, usize requested_size) override { + if (maybe_allocation != nullptr) unreachable(); + + return nullptr; + } + + void deallocate(void * allocation) override { + if (allocation != nullptr) unreachable(); + } + } a; + + return a; + } + + /** + * Attempts to format and print `value` as an unsigned integer out to `output`. + * + * The returned [expected] can be used to introspect if `output` encountered any issues during + * printing, otherwise it will contain the number of characters used to print `value` as text. + */ + expected print_unsigned(writer & output, u64 value) { + if (value == 0) return output.write(slice{"0"}.as_bytes()); + + u8 buffer[20]{0}; + usize buffer_count{0}; + + while (value != 0) { + constexpr usize radix{10}; + + buffer[buffer_count] = static_cast((value % radix) + '0'); + value = (value / radix); + buffer_count += 1; + } + + usize const half_buffer_count{buffer_count / 2}; + + for (usize i = 0; i < half_buffer_count; i += 1) + swap(buffer[i], buffer[buffer_count - i - 1]); + + return output.write({buffer, buffer_count}); + } +}