From 168d5375a44cb5a156b327670a6aee790ad18938 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 20 Feb 2023 01:28:58 +0000 Subject: [PATCH] Complete reader / writer interface rework --- source/app.cpp | 305 ++++++++++++++++++++++--------------- source/coral.cpp | 4 +- source/coral/files.cpp | 11 -- source/kym/environment.cpp | 8 +- source/oar.cpp | 188 +++++++++++++++++++++-- source/runtime.cpp | 18 ++- 6 files changed, 371 insertions(+), 163 deletions(-) diff --git a/source/app.cpp b/source/app.cpp index 56352e6..79b5401 100644 --- a/source/app.cpp +++ b/source/app.cpp @@ -11,89 +11,178 @@ import coral.math; import oar; -export namespace app { - struct directory : public coral::fs { - struct rules { - bool can_read; - - bool can_write; - }; - - directory() : path_buffer{0} {} - - void read_file(coral::path const & file_path, - coral::callable const & then) { - - if (this->prefix_length == 0) return; - - if (!this->access_rules.can_read) return; - - ::SDL_RWops * rw_ops{this->open_rw(file_path, {.can_read = true})}; - - if (rw_ops == nullptr) return; - - then([rw_ops](coral::slice const & buffer) -> size_t { - return ::SDL_RWread(rw_ops, buffer.pointer, sizeof(uint8_t), buffer.length); - }); - - ::SDL_RWclose(rw_ops); - } - - void target(coral::slice const & directory_path, rules const & access_rules) { - this->access_rules = access_rules; - this->prefix_length = coral::min(directory_path.length, path_max - 1); - - { - coral::slice const path_buffer_slice{this->path_buffer}; - - coral::copy(path_buffer_slice.sliced(0, this->prefix_length), - directory_path.sliced(0, this->prefix_length).as_bytes()); - - coral::zero(path_buffer_slice.sliced(this->prefix_length, path_max - this->prefix_length)); - } - } - - void write_file(coral::path const & file_path, - coral::callable const & then) { - - if (this->prefix_length == 0) return; - - if (!this->access_rules.can_write) return; - - ::SDL_RWops * rw_ops{this->open_rw(file_path, {.can_write = true})}; - - if (rw_ops == nullptr) return; - - then([rw_ops](coral::slice const & buffer) -> size_t { - return ::SDL_RWwrite(rw_ops, buffer.pointer, sizeof(uint8_t), buffer.length); - }); - - ::SDL_RWclose(rw_ops); - } - - private: - static constexpr coral::usize path_max = 4096; - - rules access_rules; - - coral::usize prefix_length; - - coral::u8 path_buffer[path_max]; - - ::SDL_RWops * open_rw(coral::path const & file_path, rules const & file_rules) { - coral::u8 * const path_begin{this->path_buffer + this->prefix_length}; - - coral::slice const path_remaining = - {path_begin, path_begin + (path_max - this->prefix_length) - 1}; - - if (path_remaining.length < file_path.byte_size()) return nullptr; - - coral::copy(path_remaining, file_path.as_slice().as_bytes()); - - return ::SDL_RWFromFile(reinterpret_cast(this->path_buffer), "r"); - } +struct file_reader : public coral::file_reader { + enum class [[nodiscard]] close_result { + ok, + io_unavailable, }; + enum class [[nodiscard]] open_result { + ok, + io_unavailable, + access_denied, + not_found, + }; + + file_reader(coral::fs * fs) : rw_ops{nullptr} { + this->fs = fs; + } + + close_result close() { + if (::SDL_RWclose(this->rw_ops) != 0) return close_result::io_unavailable; + + this->rw_ops = nullptr; + + return close_result::ok; + } + + bool is_open() const { + return this->rw_ops != nullptr; + } + + open_result open(coral::path const & file_path) { + if (this->is_open()) switch (this->close()) { + case close_result::ok: break; + case close_result::io_unavailable: return open_result::io_unavailable; + default: coral::unreachable(); + } + + this->rw_ops = ::SDL_RWFromFile(reinterpret_cast(this->path_buffer), "r"); + + if (this->rw_ops == nullptr) return open_result::not_found; + + return open_result::ok; + } + + coral::expected read(coral::slice const & buffer) override { + if (!this->is_open()) return coral::io_error::unavailable; + + coral::usize const bytes_read{::SDL_RWread(this->rw_ops, buffer.pointer, sizeof(uint8_t), buffer.length)}; + + if ((bytes_read == 0) && (::SDL_GetError() != nullptr)) return coral::io_error::unavailable; + + return bytes_read; + } + + coral::expected seek(coral::u64 offset) override { + if (!this->is_open()) return coral::io_error::unavailable; + + // TODO: Fix cast. + coral::i64 const byte_position{ + ::SDL_RWseek(this->rw_ops, static_cast(offset), RW_SEEK_SET)}; + + if (byte_position == -1) return coral::io_error::unavailable; + + return static_cast(byte_position); + } + + coral::expected tell() override { + if (!this->is_open()) return coral::io_error::unavailable; + + coral::i64 const byte_position{::SDL_RWseek(this->rw_ops, 0, RW_SEEK_SET)}; + + if (byte_position == -1) return coral::io_error::unavailable; + + return static_cast(byte_position); + } + + private: + static constexpr coral::usize path_max{4096}; + + coral::u8 path_buffer[path_max]; + + coral::fs * fs; + + ::SDL_RWops * rw_ops; +}; + +struct base_directory : public coral::fs { + base_directory() : directory_path{} { + char * const path{::SDL_GetBasePath()}; + + if (path == nullptr) return; + + coral::usize path_length{0}; + + while (path[path_length] != 0) path_length += 1; + + if (path_length == 0) { + ::SDL_free(path); + + return; + } + + this->directory_path = {path, path_length}; + } + + ~base_directory() override { + ::SDL_free(this->directory_path.begin()); + } + + void read_file(coral::path const & file_path, coral::callable const & then) override { + if (this->directory_path.length == 0) return; + + file_reader reader{this}; + + if (reader.open(file_path) != file_reader::open_result::ok) return; + + then(reader); + + if (reader.close() != file_reader::close_result::ok) return; + } + + void write_file(coral::path const & file_path, coral::callable const & then) override { + // Directory is read-only. + } + + protected: + coral::slice directory_path; +}; + +struct user_directory : public coral::fs { + user_directory(coral::path const & title) : directory_path{} { + char * const path{::SDL_GetPrefPath("ona", title.begin())}; + + if (path == nullptr) return; + + coral::usize path_length{0}; + + while (path[path_length] != 0) path_length += 1; + + if (path_length == 0) { + ::SDL_free(path); + + return; + } + + this->directory_path = {path, path_length}; + } + + ~user_directory() override { + ::SDL_free(this->directory_path.begin()); + } + + void read_file(coral::path const & file_path, coral::callable const & then) override { + if (this->directory_path.length == 0) return; + + file_reader reader{this}; + + if (reader.open(file_path) != file_reader::open_result::ok) return; + + then(reader); + + if (reader.close() != file_reader::close_result::ok) return; + } + + void write_file(coral::path const & file_path, coral::callable const & then) override { + // Directory is read-only. + } + + protected: + coral::slice directory_path; +}; + +export namespace app { enum class log_level { notice, warning, @@ -101,48 +190,10 @@ export namespace app { }; struct system { - system(coral::path const & title) : res_archive{} { - constexpr directory::rules read_only_rules = {.can_read = true}; + system(coral::path const & title) : res{&base, "base_directory.oar"}, user{title} {} - { - char * const path{::SDL_GetBasePath()}; - - if (path == nullptr) { - this->cwd_directory.target("./", read_only_rules); - } else { - coral::usize path_length{0}; - - while (path[path_length] != 0) path_length += 1; - - if (path_length == 0) { - this->cwd_directory.target("./", read_only_rules); - } else { - this->cwd_directory.target({path, path_length}, read_only_rules); - } - } - - ::SDL_free(path); - } - - { - char * const path{::SDL_GetPrefPath("ona", title.begin())}; - - if (path != nullptr) { - coral::usize path_length{0}; - - while (path[path_length] != 0) path_length += 1; - - if (path_length != 0) this->cwd_directory.target({path, path_length}, { - .can_read = true, - }); - } - - ::SDL_free(path); - } - } - - coral::fs & cwd_fs() { - return this->cwd_directory; + coral::fs & base_fs() { + return this->base; } bool poll() { @@ -156,7 +207,7 @@ export namespace app { } coral::fs & res_fs() { - return this->res_archive; + return this->res; } void log(app::log_level level, coral::slice const & message) { @@ -172,7 +223,7 @@ export namespace app { } coral::fs & user_fs() { - return this->user_directory; + return this->user; } private: @@ -188,11 +239,11 @@ export namespace app { } } allocator; - oar::archive res_archive; + base_directory base; - directory cwd_directory; + user_directory user; - directory user_directory; + oar::archive res; }; struct graphics { diff --git a/source/coral.cpp b/source/coral.cpp index 3e530e5..7dcaf2a 100644 --- a/source/coral.cpp +++ b/source/coral.cpp @@ -38,13 +38,13 @@ export namespace coral { using u32 = uint32_t; - using i32 = uint32_t; + using i32 = int32_t; usize const i32_max = 0xffffffff; using u64 = uint64_t; - using i64 = uint64_t; + using i64 = int64_t; using f32 = float; diff --git a/source/coral/files.cpp b/source/coral/files.cpp index 8a59106..141a970 100644 --- a/source/coral/files.cpp +++ b/source/coral/files.cpp @@ -118,12 +118,6 @@ export namespace coral { * Platform-generalized file system interface. */ struct fs { - struct io_rules { - bool can_read; - - bool can_write; - }; - virtual ~fs() {}; /** @@ -134,11 +128,6 @@ export namespace coral { */ virtual void read_file(path const & file_path, callable const & then) = 0; - /** - * Returns the [io_rules] that this file system interface abides by. - */ - virtual io_rules rules() const = 0; - /** * Attempts to write the file in the file system located at `file_path` relative, calling * `then` if it was successfully opened for writing. diff --git a/source/kym/environment.cpp b/source/kym/environment.cpp index 11ce35d..f668ae8 100644 --- a/source/kym/environment.cpp +++ b/source/kym/environment.cpp @@ -69,8 +69,8 @@ export namespace kym { return nil; } - coral::expected default_stringify(vm & owning_vm, void * userdata, coral::writable const & output) { - return output(coral::slice{"[object]"}.as_bytes()); + coral::expected default_stringify(vm & owning_vm, void * userdata, coral::writer & output) { + return output.write(coral::slice{"[object]"}.as_bytes()); } struct bound_object { @@ -81,7 +81,7 @@ export namespace kym { value(*call)(vm &, void *, coral::slice const &); - coral::expected(*stringify)(vm &, void *, coral::writable const &); + coral::expected(*stringify)(vm &, void *, coral::writer &); } behavior; bound_object(vm * owning_vm) : userdata{nullptr}, owning_vm{owning_vm}, behavior{ @@ -98,7 +98,7 @@ export namespace kym { return this->behavior.call(*this->owning_vm, this->userdata, arguments); } - coral::expected stringify(coral::writable const & output) { + coral::expected stringify(coral::writer & output) { return this->behavior.stringify(*this->owning_vm, this->userdata, output); } diff --git a/source/oar.cpp b/source/oar.cpp index f2b3e5d..63c8312 100644 --- a/source/oar.cpp +++ b/source/oar.cpp @@ -3,36 +3,200 @@ export module oar; import coral; import coral.files; +constexpr coral::usize signature_length{4}; + +constexpr coral::usize signature_version_length{1}; + +constexpr coral::usize signature_identifier_length{signature_length - signature_version_length}; + +constexpr coral::u8 signature_magic[signature_length]{'o', 'a', 'r', 0}; + +struct header { + coral::u8 signature_magic[signature_length]; + + coral::u32 entry_count; + + coral::u8 padding[504]; +}; + +static_assert(sizeof(header) == 512); + +struct entry { + coral::path path; + + coral::u64 data_offset; + + coral::u64 data_length; + + coral::u8 padding[240]; +}; + +static_assert(sizeof(entry) == 512); + export namespace oar { - constexpr coral::usize signature_length{4}; + struct archive_file_reader : public coral::file_reader { + enum class [[nodiscard]] close_result { + ok, + }; - constexpr coral::u8 signature_magic[signature_length]{'o', 'a', 'r', 0}; + enum class [[nodiscard]] open_result { + ok, + io_unavailable, + archive_invalid, + archive_unsupported, + not_found, + }; - struct entry { - coral::u8 signature_magic[signature_length]; + archive_file_reader(coral::file_reader * archive_reader) { + this->archive_reader = archive_reader; + this->data_offset = 0; + this->data_length = 0; + this->data_cursor = 0; + } - coral::path path; + close_result close() { + return close_result::ok; + } + + bool is_open() const { + return this->data_offset >= sizeof(header); + } + + open_result open(coral::path const & file_path) { + if (this->is_open()) switch (this->close()) { + case close_result::ok: break; + default: coral::unreachable(); + } + + if (!this->archive_reader->seek(0).is_ok()) return open_result::io_unavailable; + + constexpr coral::usize header_size = sizeof(header); + coral::u8 archive_header_buffer[header_size]{0}; + + { + coral::expected const read_bytes{archive_reader->read(archive_header_buffer)}; + + if ((!read_bytes.is_ok()) || (read_bytes.value() != header_size)) + return open_result::archive_invalid; + } + + header const * const archive_header{reinterpret_cast
(archive_header_buffer)}; + + if (!coral::equals(coral::slice{archive_header->signature_magic, + signature_identifier_length}, coral::slice{signature_magic, + signature_identifier_length})) return open_result::archive_invalid; + + if (archive_header->signature_magic[signature_identifier_length] != + signature_magic[signature_identifier_length]) return open_result::archive_unsupported; + + // Read file table. + coral::u64 head{0}; + coral::u64 tail{archive_header->entry_count - 1}; + constexpr coral::usize entry_size{sizeof(entry)}; + coral::u8 file_entry_buffer[entry_size]{0}; + + while (head <= tail) { + coral::u64 const midpoint{head + ((tail - head) / 2)}; + + if (!archive_reader->seek(header_size + (entry_size * midpoint)).is_ok()) + return open_result::archive_invalid; + + { + coral::expected const read_bytes{archive_reader->read(file_entry_buffer)}; + + if ((!read_bytes.is_ok()) || (read_bytes.value() != header_size)) + return open_result::archive_invalid; + } + + entry const * const archive_entry{reinterpret_cast(file_entry_buffer)}; + coral::size const comparison{file_path.compare(archive_entry->path)}; + + if (comparison == 0) { + this->data_offset = archive_entry->data_offset; + this->data_length = archive_entry->data_length; + this->data_cursor = archive_entry->data_offset; + + return open_result::ok; + } + + if (comparison > 0) { + head = (midpoint + 1); + } else { + tail = (midpoint - 1); + } + } + + return open_result::not_found; + } + + coral::expected read(coral::slice const & buffer) override { + if (!this->is_open()) return coral::io_error::unavailable; + + if (this->archive_reader->seek(this->data_offset + this->data_cursor).is_ok()) + return coral::io_error::unavailable; + + coral::expected const bytes_read{this->archive_reader->read(buffer.sliced(0, + coral::min(buffer.length, static_cast(( + this->data_offset + this->data_length) - this->data_cursor))))}; + + if (!bytes_read.is_ok()) this->data_cursor += bytes_read.value(); + + return bytes_read; + } + + coral::expected seek(coral::u64 offset) override { + if (!this->is_open()) return coral::io_error::unavailable; + + return coral::io_error::unavailable; + } + + coral::expected tell() override { + if (!this->is_open()) return coral::io_error::unavailable; + + return this->data_cursor; + } + + private: + coral::file_reader * archive_reader; coral::u64 data_offset; coral::u64 data_length; - coral::u8 padding[244]; + coral::u64 data_cursor; }; struct archive : public coral::fs { - archive() { - + archive(coral::fs * backing_fs, coral::path const & archive_path) { + this->backing_fs = backing_fs; + this->archive_path = archive_path; } void read_file(coral::path const & file_path, - coral::callable const & then) override { + coral::callable const & then) override { + + if ((this->backing_fs == nullptr) || (this->archive_path.byte_size() == 0)) return; + + this->backing_fs->read_file(this->archive_path, [&](coral::file_reader & archive_reader) { + archive_file_reader file_reader{&archive_reader}; + + if (file_reader.open(file_path) != archive_file_reader::open_result::ok) return; + + then(file_reader); + + if (file_reader.close() != archive_file_reader::close_result::ok) return; + }); } void write_file(coral::path const & file_path, - coral::callable const & then) override { + coral::callable const & then) override { + + // Read-only file system. } + + private: + coral::fs * backing_fs; + + coral::path archive_path; }; } - -static_assert(sizeof(oar::entry) == 512); diff --git a/source/runtime.cpp b/source/runtime.cpp index 4bf31d7..9aaf4da 100644 --- a/source/runtime.cpp +++ b/source/runtime.cpp @@ -15,7 +15,7 @@ extern "C" int main(int argc, char const * const * argv) { constexpr coral::path config_path{"config.kym"}; bool is_config_loaded{false}; - system.res_fs().read_file(config_path, [&](coral::readable const & file) { + system.res_fs().read_file(config_path, [&](coral::reader & file) { coral::allocator * const allocator{&system.thread_safe_allocator()}; kym::vm vm{allocator, [&system](coral::slice const & error_message) { @@ -29,10 +29,13 @@ extern "C" int main(int argc, char const * const * argv) { } coral::stack script_source{allocator}; - coral::u8 stream_buffer[1024]{0}; - if (!coral::stream(coral::sequence_writer{&script_source}, file, stream_buffer).is_ok()) - return; + { + coral::u8 stream_buffer[1024]{0}; + coral::sequence_writer script_writer{&script_source}; + + if (!coral::stream(script_writer, file, stream_buffer).is_ok()) return; + } vm.with_object(vm.compile(coral::slice{script_source.begin(), script_source.end()}.as_chars()), [&](kym::bound_object & script) { vm.with_object(script.call({}), [&](kym::bound_object & config) { @@ -51,8 +54,9 @@ extern "C" int main(int argc, char const * const * argv) { vm.with_object(config.get_field("title"), [&](kym::bound_object & title) { coral::stack title_buffer{&system.thread_safe_allocator()}; + coral::sequence_writer title_writer{&title_buffer}; - if (!title.stringify(coral::sequence_writer{&title_buffer}).is_ok()) { + if (!title.stringify(title_writer).is_ok()) { system.log(app::log_level::error, "failed to decode `title` property of config"); @@ -71,10 +75,10 @@ extern "C" int main(int argc, char const * const * argv) { { coral::sequence_writer error_writer{&error_message}; - if (!error_writer(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; - if (!error_writer(config_path.as_slice().as_bytes()).is_ok()) + if (!error_writer.write(config_path.as_slice().as_bytes()).is_ok()) return coral::u8_max; }