export module coral.io; import coral; export namespace coral { /** * Multiplexing byte-based ring buffer that may be used for memory-backed I/O operations and fast data (re)- * construction. */ struct ring_buffer : public writer, public reader { /** * Potential results from attempting to allocate a ring buffer dynamically. * * [allocate_result::ok] means nothing went wrong and the allocation was successful. * * [allocate_result::out_of_memory] indicates that there is not enough memory available in the provided * allocator to create a buffer of the requested size. */ enum class [[nodiscard]] allocate_result { ok, out_of_memory, }; ring_buffer() = default; ring_buffer(ring_buffer const &) = delete; ~ring_buffer() override { if (this->data_allocator != nullptr) this->data_allocator->deallocate(this->data.pointer); } /** * Attempts to allocate memory for the ring buffer `capacity` number of bytes large with `data_allocator` as the * allocation strategy. * * See [allocate_result] for more information on error handling. * * *Note*: calling allocate will wipe out the contents of the current buffer. */ allocate_result allocate(allocator & data_allocator, usize capacity) { if (this->data_allocator != nullptr) this->data_allocator->deallocate(this->data.pointer); this->clear(); u8 * const data {this->data_allocator->reallocate(nullptr, capacity)}; if (data == nullptr) { this->data_allocator = nullptr; this->data = {}; return allocate_result::out_of_memory; } this->data_allocator = &data_allocator; this->data = {data, capacity}; return allocate_result::ok; } /** * Returns a mutable [slice] ranging from the head to the last-filled element. * * *Note*: The lifetime and validity of the returned slice is only guaranteed for as long as the source * [ring_buffer] is not mutated or out-of-scope. */ slice as_slice() const { return this->data; } /** * Clears all written elements from the buffer. */ void clear() { this->data_filled = 0; this->read_index = 0; this->write_index = 0; } /** * Returns the number of bytes in the buffer that have been filled with data. */ usize filled() const { return this->data_filled; } /** * Returns `true` if the buffer is completely empty of data, otherwise `false`. */ bool is_empty() const { return this->data_filled == this->data.length; } /** * Returns `true` if the buffer has been completely filled with data, otherwise `false`. */ bool is_full() const { return this->data_filled == this->data.length; } /** * Attempts to write the single value `data` into the buffer, returning `true` if successful, otherwise `false` * if the buffer is full. */ bool put(u8 data) { if (this->is_full()) return false; this->data_filled += 1; this->data[this->write_index] = data; this->write_index = (this->write_index + 1) % this->data.length; return true; } /** * 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.sliced(0, min(this->data_filled, data.length))}; this->data_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) % this->data.length; } return readable_data.length; } /** * Returns the remaining unfilled buffer space in bytes. */ usize remaining() const { return this->data.length - this->data_filled; } /** * Write `data` to the buffer, returning the number of bytes written to the buffer. */ expected write(slice const & data) override { slice const writable_data {data.sliced(0, min(data.length, this->remaining()))}; this->data_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) % this->data.length; } return writable_data.length; } private: usize data_filled {0}; usize read_index {0}; usize write_index {0}; allocator * data_allocator {nullptr}; slice data {}; }; /** * 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) { u64 total_bytes_written {0}; expected bytes_read {input.read(buffer)}; if (bytes_read.is_error()) return *bytes_read.error(); usize read {*bytes_read.ok()}; while (read != 0) { expected const bytes_written = output.write(buffer.sliced(0, read)); if (bytes_written.is_error()) return *bytes_read.error(); total_bytes_written += *bytes_written.ok(); bytes_read = input.read(buffer); if (bytes_read.is_error()) return *bytes_read.error(); read = *bytes_read.ok(); } 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}); } template expected println(writer & output, arguments... print_arguments) { // TODO: Implement. } }