From d5b4281d3639a941a458c1f9d718110d76bf9efa Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 18 Feb 2023 03:34:40 +0000 Subject: [PATCH 01/35] Port codebase to C++20 --- .drone.yml | 6 +- .gitignore | 5 +- .vscode/c_cpp_properties.json | 16 + .vscode/launch.json | 21 +- .vscode/settings.json | 21 +- .vscode/tasks.json | 60 ---- build.py | 35 ++ build.zig | 34 -- config.kym | 6 + ona.lua | 8 - readme.md | 73 ++++ source/app.cpp | 95 +++++ source/app/sdl.cpp | 162 +++++++++ source/core.cpp | 272 ++++++++++++++ source/core/image.cpp | 31 ++ source/core/math.cpp | 19 + source/core/sequence.cpp | 118 +++++++ source/kym.cpp | 49 +++ source/kym/environment.cpp | 144 ++++++++ source/runtime.cpp | 81 +++++ src/io.zig | 148 -------- src/main.zig | 50 --- src/mem.zig | 87 ----- src/stack.zig | 117 ------- src/sys.zig | 642 ---------------------------------- 25 files changed, 1122 insertions(+), 1178 deletions(-) create mode 100644 .vscode/c_cpp_properties.json mode change 100755 => 100644 .vscode/launch.json mode change 100755 => 100644 .vscode/settings.json delete mode 100755 .vscode/tasks.json create mode 100755 build.py delete mode 100644 build.zig create mode 100644 config.kym delete mode 100644 ona.lua create mode 100644 readme.md create mode 100644 source/app.cpp create mode 100644 source/app/sdl.cpp create mode 100644 source/core.cpp create mode 100644 source/core/image.cpp create mode 100644 source/core/math.cpp create mode 100644 source/core/sequence.cpp create mode 100644 source/kym.cpp create mode 100644 source/kym/environment.cpp create mode 100644 source/runtime.cpp delete mode 100644 src/io.zig delete mode 100644 src/main.zig delete mode 100644 src/mem.zig delete mode 100755 src/stack.zig delete mode 100644 src/sys.zig diff --git a/.drone.yml b/.drone.yml index f9941e1..5249bc7 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3,7 +3,7 @@ name: continuous integration steps: - name: build & test - image: euantorano/zig:0.9.1 + image: ubuntu:jammy commands: - - zig build test - - $(find zig-cache -name test) main.zig + - sudo apt install -y gcc python3.10 + - python3.10 ./build.py diff --git a/.gitignore b/.gitignore index 4c14adf..fa7dadd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -**/zig-out/ -**/zig-cache/ +cache +runtime +runtime.exe diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..1fabea9 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/src" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu17", + "cppStandard": "gnu++20", + "intelliSenseMode": "linux-gcc-x64" + } + ], + "version": 4 +} diff --git a/.vscode/launch.json b/.vscode/launch.json old mode 100755 new mode 100644 index a37980f..7b90bde --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,23 +2,20 @@ "version": "0.2.0", "configurations": [ { - "name": "Build", + "name": "Runtime", "type": "gdb", "request": "launch", - "target": "${workspaceFolder}/zig-out/bin/ona", + "target": "./runtime", "cwd": "${workspaceRoot}", - "valuesFormatting": "parseText", - "preLaunchTask": "Build", + "valuesFormatting": "parseText" }, { - "name": "Test", - "type": "gdb", + "name": "Build Script", + "type": "python", "request": "launch", - "target": "${workspaceFolder}/zig-cache/o/b57ef32c79a05339fbe4a8eb648ff6df/test", - "arguments": "main.zig", - "cwd": "${workspaceRoot}", - "valuesFormatting": "parseText", - "preLaunchTask": "Build Test", - }, + "program": "./build.py", + "console": "integratedTerminal", + "justMyCode": true + } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json old mode 100755 new mode 100644 index 4beb35f..15daf29 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,17 +1,8 @@ { - "editor.rulers": [100], - - "files.exclude":{ - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, - "**/.DS_Store": true, - "**/Thumbs.db": true, - "**/zig-cache": true, - "**/zig-out": true, - }, - - "git.detectSubmodulesLimit": 0, - "git.ignoreSubmodules": true, + "files.associations": { + "type_traits": "cpp", + "cassert": "cpp", + "cstddef": "cpp", + "string_view": "cpp" + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100755 index 016ab6a..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "version": "2.0.0", - - "tasks": [ - { - "label": "Build", - "type": "shell", - "command": "zig build", - - "group": { - "kind": "build", - "isDefault": true - }, - - "presentation": { - "echo": true, - "reveal": "always", - "focus": true, - "panel": "shared", - "showReuseMessage": true, - "clear": true, - "revealProblems": "onProblem", - }, - - "problemMatcher": { - "source": "gcc", - "owner": "cpptools", - - "fileLocation": [ - "autoDetect", - "${cwd}", - ], - - "pattern": { - "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", - "file": 1, - "line": 2, - "column": 3, - "severity": 4, - "message": 5, - } - } - }, - { - "label": "Test", - "type": "shell", - "command": "$(find zig-cache -name test) src/main.zig", - "group": { - "kind": "test", - "isDefault": true - }, - }, - { - "label": "Build Test", - "type": "shell", - "command": "zig build test", - "group": "test" - }, - ], -} diff --git a/build.py b/build.py new file mode 100755 index 0000000..41c3cb1 --- /dev/null +++ b/build.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 + +import os +import subprocess + +source_path = "./source/" +cache_path = "./cache/" + +if not(os.path.exists(cache_path)): + os.mkdir(cache_path) + +compile_command = f"clang++ -std=c++20 -fno-exceptions -fmodules -fprebuilt-module-path=./cache" +object_file_paths = [] + +def compile_module(source_file_path, module_identifier) -> None: + output_path = os.path.join(cache_path, module_identifier) + + subprocess.run(f"{compile_command} -Xclang -emit-module-interface -c {source_file_path} -o {output_path}.pcm", shell=True, check=True) + subprocess.run(f"{compile_command} -c {source_file_path} -o {output_path}.o", shell=True, check=True) + object_file_paths.append(f"{output_path}.o") + +def compile_package(root_module_name: str) -> None: + root_module_source_path = os.path.join(source_path, root_module_name) + + compile_module(f"{root_module_source_path}.cpp", root_module_name) + + if os.path.isdir(root_module_source_path): + for file_name in os.listdir(root_module_source_path): + compile_module(os.path.join(root_module_source_path, file_name), f"{root_module_name}.{os.path.splitext(file_name)[0]}") + +compile_package("core") +compile_package("app") +compile_package("kym") +compile_package("runtime") +subprocess.run(f"{compile_command} {' '.join(object_file_paths)} -o ./runtime -lSDL2", shell=True, check=True) diff --git a/build.zig b/build.zig deleted file mode 100644 index 7eef4a8..0000000 --- a/build.zig +++ /dev/null @@ -1,34 +0,0 @@ -const std = @import("std"); - -pub fn build(builder: *std.build.Builder) void { - const target = builder.standardTargetOptions(.{}); - const mode = builder.standardReleaseOptions(); - - // Ona executable. - { - const ona_exe = builder.addExecutable("ona", "./src/main.zig"); - - ona_exe.setTarget(target); - ona_exe.setBuildMode(mode); - ona_exe.install(); - ona_exe.addIncludeDir("./ext"); - ona_exe.linkSystemLibrary("SDL2"); - - const run_cmd = ona_exe.run(); - - run_cmd.step.dependOn(builder.getInstallStep()); - - if (builder.args) |args| run_cmd.addArgs(args); - - builder.step("run", "Run Ona application").dependOn(&run_cmd.step); - } - - // Ona tests. - { - const ona_tests = builder.addTestExe("test", "./src/main.zig"); - - ona_tests.setTarget(target); - ona_tests.setBuildMode(mode); - builder.step("test", "Run Ona unit tests").dependOn(&ona_tests.step); - } -} diff --git a/config.kym b/config.kym new file mode 100644 index 0000000..187e4f2 --- /dev/null +++ b/config.kym @@ -0,0 +1,6 @@ + +return { + title = "Demo", + width = 640, + height = 480, +} diff --git a/ona.lua b/ona.lua deleted file mode 100644 index 286067e..0000000 --- a/ona.lua +++ /dev/null @@ -1,8 +0,0 @@ - -return { - name = "Ona", - initial_width = 1280, - initial_height = 800, - - initial_scene = nil, -} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..ce4dcbb --- /dev/null +++ b/readme.md @@ -0,0 +1,73 @@ +# Ona + +## Table of Contents + +1. [Overview](#overview) +1. [Goals](#goals) +1. [Technical Details](#technical-details) + 1. [Requirements](#requirements) + 1. [Building](#building) + 1. [Project Structure](#project-structure) + 1. [No Headers](#no-headers) + 1. [All Code is Equal](#all-code-is-equal) + +## Overview + +Ona is a straightforward game engine with the aim of staying reasonably lightweight through its + +Ona is also the Catalan word for "wave". + +## Goals + +* Fully-featured two-dimensional raster and vector-derived rendering capabilities. + +* Support major computer gaming ecosystems; Namely Microsoft Windows, SteamOS, and GNU Linux systems running on X11 or Wayland. + +* Avoid shipping external dependencies beyond the executible itself. + +* Be lightweight in base engine memory usage and disk size. + +* Provide a simple scene graph system that translates its graph nodes into a cache-friendly representation at runtime. + +* Provide an execution-speed optimized scripting interface through a Lua-inspired language named "Kym", with features like first-class support for common mathematic types used in rendering. + +* One data serialization and configuration system to rule them all backed by the Kym scripting language. + +* Opt-in overhead via a native C plug-in interface that allows for systems-level programmers to easily extend engine-level functionality and scripting language library tools. + +## Technical Details + +### Requirements + +Ona currently depends the following third-party tools to build it: + + * GNU C++ compiler with full C++20 support or above. + * Python interpreter version 3.10 or above. + +Additionally, Ona depends on the following third-party system-wide dependencies: + + * SDL2 version 2.0.20 or above. + +As the project evolves, dependencies on libraries external to the project codebase will be minimized or removed outright to meet the goals of the project as closely as possible. + +### Building + +Once all third-party tools and system-wide dependencies are satisfied, navigate to the root project folder and run the `./build.py` Python build script. + +By default, the build script will build the engine runtime, required for running games built with Ona, in release-debug mode. + +### Project Structure + +As Ona uses C++20, it is able to make use of the new modules language feature. While this brings with it a number of drawbacks, like a lack of widescale vendor adoption, it also provides some key benefits. + +#### No Headers + +All first-party code in the project is free of headers. Code is grouped in a module and package dichotomy, where each `.cpp` file in the root source directory represents the common package of a module grouping. + +Subdirectories then build further abstractions atop these common module files. For example, the `core.cpp` source file contains many common memory manipulation and floating point mathematics utilities, which are made use of in `core/image.cpp` for modifying CPU-bound pixel data. + +#### All Code is Equal + +Following on from no headers necessary, declarations, template metaprogramming, and definitions all go into the same place now. A typical Ona source file mixes all of these, traditionally separate, pieces of logic together in shared `.cpp` files. + +Alongside the surface-level benefit of writing having fewer lines of code, this also means there is less work necessary to maintain the codebase at large and a smaller space to create duplication errors in. diff --git a/source/app.cpp b/source/app.cpp new file mode 100644 index 0000000..5a4a1b3 --- /dev/null +++ b/source/app.cpp @@ -0,0 +1,95 @@ +export module app; + +import core; +import core.image; +import core.math; + +export namespace app { + struct path { + static constexpr core::usize max = 0xff; + + static constexpr char seperator = '/'; + + core::u8 buffer[max + 1]; + + static constexpr path empty() { + path empty_path = {0}; + + empty_path.buffer[max] = max; + + return empty_path; + } + + core::slice as_slice() const { + return {reinterpret_cast(this->buffer), this->size()}; + } + + constexpr core::usize size() const { + return max - this->buffer[max]; + } + + core::i16 compare(path const & that) { + return 0; + } + + bool equals(path const & that) { + return core::equals(this->as_slice(), that.as_slice()); + } + + constexpr path joined(core::slice const & text) const { + if (text.length > this->buffer[max]) return empty(); + + path joined_path = *this; + + for (char const c : text) { + joined_path.buffer[joined_path.size()] = c; + joined_path.buffer[max] -= 1; + } + + return joined_path; + } + + core::u64 hash() { + return 0; + } + }; + + struct file_store { + virtual void read_file(app::path const & file_path, core::callable const & then) = 0; + }; + + enum class log_level { + notice, + warning, + error, + }; + + struct system { + virtual bool poll() = 0; + + virtual file_store & bundle() = 0; + + virtual void log(log_level level, core::slice const & message) = 0; + + virtual core::allocator & thread_safe_allocator() = 0; + }; + + struct graphics { + enum class show_error { + none, + out_of_memory, + }; + + struct canvas { + core::color background_color; + }; + + virtual void render(canvas & source_canvas) = 0; + + virtual void present() = 0; + + virtual show_error show(core::u16 physical_width, core::u16 physical_height) = 0; + + virtual void retitle(core::slice const & updated_title) = 0; + }; +} diff --git a/source/app/sdl.cpp b/source/app/sdl.cpp new file mode 100644 index 0000000..ce1825c --- /dev/null +++ b/source/app/sdl.cpp @@ -0,0 +1,162 @@ +module; + +#include + +export module app.sdl; + +import app; + +import core; +import core.image; +import core.sequence; +import core.math; + +struct bundled_file_store : public app::file_store { + void read_file(app::path const & file_path, core::callable const & then) override { + // Path is guaranteed to never be greater than 512 characters long (file_path is max 256 and prefix is 2). + core::stack path_buffer{&core::null_allocator()}; + + if (path_buffer.append("./").has_value()) core::unreachable(); + + // File path is guaranteed to be null-terminated. + if (path_buffer.append(file_path.as_slice()).has_value()) core::unreachable(); + + SDL_RWops * rw_ops = ::SDL_RWFromFile(path_buffer.as_slice().pointer, "r"); + + if (rw_ops == nullptr) return; + + then([rw_ops](core::slice const & buffer) -> size_t { + return ::SDL_RWread(rw_ops, buffer.pointer, sizeof(uint8_t), buffer.length); + }); + + ::SDL_RWclose(rw_ops); + } +}; + +struct sdl_allocator : public core::allocator { + core::u8 * reallocate(core::u8 * maybe_allocation, core::usize requested_size) override { + return reinterpret_cast(::SDL_malloc(requested_size)); + } + + void deallocate(void * allocation) override { + ::SDL_free(allocation); + } +}; + +struct sdl_system : public app::system { + private: + ::SDL_Event sdl_event; + + sdl_allocator allocator; + + bundled_file_store bundled_store; + + public: + sdl_system() : + sdl_event{0}, + allocator{} {} + + bool poll() override { + while (::SDL_PollEvent(&this->sdl_event) != 0) { + switch (this->sdl_event.type) { + case SDL_QUIT: return false; + } + } + + return true; + } + + app::file_store & bundle() override { + return this->bundled_store; + } + + void log(app::log_level level, core::slice const & message) override { + core::i32 const length = static_cast( + core::min(message.length, static_cast(core::i32_max))); + + ::SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, + SDL_LOG_PRIORITY_INFO, "%.*s", length, message.pointer); + } + + core::allocator & thread_safe_allocator() override { + return this->allocator; + } +}; + +struct sdl_graphics : public app::graphics { + static constexpr core::usize title_maximum = 128; + + core::u32 title_length; + + char title_buffer[title_maximum]; + + ::SDL_Window * sdl_window = nullptr; + + ::SDL_Renderer * sdl_renderer = nullptr; + + sdl_graphics(core::slice const & title) { + this->retitle(title); + } + + void render(canvas & source_canvas) override { + if (this->sdl_renderer != nullptr) { + SDL_SetRenderDrawColor(this->sdl_renderer, source_canvas.background_color.to_r8(), + source_canvas.background_color.to_g8(), source_canvas.background_color.to_b8(), + source_canvas.background_color.to_a8()); + + SDL_RenderClear(this->sdl_renderer); + } + } + + void retitle(core::slice const & title) override { + this->title_length = core::min(title.length, title_maximum - 1); + + for (core::usize i = 0; i < this->title_length; i += 1) title_buffer[i] = title[i]; + + for (core::usize i = this->title_length; i < title_maximum; i += 1) title_buffer[i] = 0; + + if (this->sdl_window != nullptr) ::SDL_SetWindowTitle(this->sdl_window, title_buffer); + } + + app::graphics::show_error show(core::u16 physical_width, core::u16 physical_height) override { + if (this->sdl_window == nullptr) { + constexpr int sdl_windowpos = SDL_WINDOWPOS_UNDEFINED; + constexpr core::u32 sdl_windowflags = 0; + + this->sdl_window = ::SDL_CreateWindow(title_buffer, sdl_windowpos, sdl_windowpos, + static_cast(physical_width), static_cast(physical_height), + sdl_windowflags); + + if (this->sdl_window == nullptr) return show_error::out_of_memory; + } else { + ::SDL_ShowWindow(this->sdl_window); + } + + if (this->sdl_renderer == nullptr) { + constexpr core::u32 sdl_rendererflags = 0; + + this->sdl_renderer = ::SDL_CreateRenderer(this->sdl_window, -1, sdl_rendererflags); + + if (this->sdl_renderer == nullptr) return show_error::out_of_memory; + } + + return show_error::none; + } + + void present() override { + if (this->sdl_renderer != nullptr) { + ::SDL_RenderPresent(this->sdl_renderer); + } + } +}; + +export namespace app { + int display(core::slice const & title, + core::callable const & run) { + + sdl_system system; + sdl_graphics graphics(title); + + return run(system, graphics); + } +} diff --git a/source/core.cpp b/source/core.cpp new file mode 100644 index 0000000..429717e --- /dev/null +++ b/source/core.cpp @@ -0,0 +1,272 @@ +module; + +#include +#include + +export module core; + +export namespace core { + using usize = size_t; + + using size = __ssize_t; + + using u8 = uint8_t; + + usize const u8_max = 0xff; + + using i8 = uint8_t; + + using u16 = uint16_t; + + usize const u16_max = 0xffff; + + using i16 = uint16_t; + + using u32 = uint32_t; + + using i32 = uint32_t; + + usize const i32_max = 0xffffffff; + + using u64 = uint32_t; + + using i64 = uint32_t; + + using f32 = float; + + using f64 = double; + + struct allocator { + allocator() = default; + + allocator(allocator const &) = delete; + + virtual ~allocator() {}; + + virtual u8 * reallocate(u8 * maybe_allocation, usize requested_size) = 0; + + virtual void deallocate(void * allocation) = 0; + }; +} + +export void * operator new(core::usize requested_size, core::u8 * allocation_placement) { + return allocation_placement; +} + +export void * operator new[](core::usize requested_size, core::u8 * allocation_placement) { + return allocation_placement; +} + +export void * operator new(core::usize requested_size, core::allocator & allocator) { + return allocator.reallocate(nullptr, requested_size); +} + +export void * operator new[](core::usize requested_size, core::allocator & allocator) { + return allocator.reallocate(nullptr, requested_size); +} + +export namespace core { + [[noreturn]] void unreachable() { + __builtin_unreachable(); + } + + template struct slice { + usize length; + + type * pointer; + + constexpr slice() : length{0}, pointer{nullptr} { + + } + + constexpr slice(char const *&& zstring) : length{0}, pointer{zstring} { + while (zstring[length] != 0) this->length += 1; + } + + constexpr slice(type * slice_pointer, usize slice_length) : length{slice_length}, pointer{slice_pointer} { + + } + + template constexpr slice(type(&array)[array_size]) : length{array_size}, pointer{array} { + + } + + slice as_bytes() const { + return {reinterpret_cast(this->pointer), this->length * sizeof(type)}; + } + + slice as_chars() const { + return {reinterpret_cast(this->pointer), this->length * sizeof(type)}; + } + + operator slice() const { + return (*reinterpret_cast const *>(this)); + } + + type & operator[](usize index) const { + return this->pointer[index]; + } + + slice after(usize index) const { + return {this->pointer + index, this->length - index}; + } + + slice until(usize index) const { + return {this->pointer, index}; + } + + slice between(usize a, usize b) const { + return {this->pointer + a, b}; + } + + constexpr type * begin() const { + return this->pointer; + } + + constexpr type * end() const { + return this->pointer + this->length; + } + }; + + template struct [[nodiscard]] optional { + optional() : buffer{0} { + + } + + optional(element const & value) : buffer{0} { + (*reinterpret_cast(this->buffer)) = value; + this->buffer[sizeof(element)] = 1; + } + + optional(optional const & that) : buffer{0} { + if (that.has_value()) { + (*reinterpret_cast(this->buffer)) = that.value(); + this->buffer[sizeof(element)] = 1; + } else { + this->buffer[sizeof(element)] = 0; + } + } + + bool has_value() const { + return this->buffer[sizeof(element)] == 1; + } + + element const & value_or(element const & fallback) const { + return this->has_value() ? *reinterpret_cast(this->buffer) : fallback; + } + + element & value() { + return *reinterpret_cast(this->buffer); + } + + element const & value() const { + return *reinterpret_cast(this->buffer); + } + + private: + u8 buffer[sizeof(element) + 1]; + }; + + template struct callable; + + template struct callable { + using function = return_type(*)(argument_types...); + + callable(function callable_function) : dispatcher(dispatch_function) { + new (this->capture) function{callable_function}; + } + + callable(callable const &) = delete; + + template callable(functor const & callable_functor) : dispatcher(dispatch_functor) { + new (this->capture) functor{callable_functor}; + } + + return_type operator()(argument_types const &... arguments) const { + return this->dispatcher(this->capture, arguments...); + } + + private: + static constexpr usize capture_size = 24; + + return_type(* dispatcher)(u8 const * userdata, argument_types... arguments); + + u8 capture[capture_size]; + + static return_type dispatch_function(u8 const * userdata, argument_types... arguments) { + return (*reinterpret_cast(userdata))(arguments...); + } + + template static return_type dispatch_functor(u8 const * userdata, argument_types... arguments) { + return (*reinterpret_cast(userdata))(arguments...); + } + }; + + 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; + } + + using readable = callable(slice const &)>; + + using writable = callable(slice const &)>; + + template bool equals(slice const & a, slice const & b) { + if (a.length != b.length) return false; + + for (size_t i = 0; i < a.length; i += 1) if (a[i] != b[i]) return false; + + return true; + } + + optional stream(writable const & output, readable const & input, slice const & buffer) { + usize written = 0; + optional maybe_read = input(buffer); + + if (!maybe_read.has_value()) return {}; + + usize read = maybe_read.value(); + + while (read != 0) { + optional const maybe_written = output(buffer.until(read)); + + if (!maybe_written.has_value()) return {}; + + written += maybe_written.value(); + maybe_read = input(buffer); + + if (!maybe_read.has_value()) return {}; + + read = maybe_read.value(); + } + + return written; + } + + template scalar max(scalar const & a, scalar const & b) { + return (a > b) ? a : b; + } + + template scalar min(scalar const & a, scalar const & b) { + return (a < b) ? a : b; + } + + template scalar clamp(scalar const & value, scalar const & min_value, scalar const & max_value) { + return max(min_value, min(max_value, value)); + } + + f32 round32(f32 value) { + return __builtin_roundf(value); + } +} diff --git a/source/core/image.cpp b/source/core/image.cpp new file mode 100644 index 0000000..0a0a75f --- /dev/null +++ b/source/core/image.cpp @@ -0,0 +1,31 @@ +export module core.image; + +import core; + +export namespace core { + struct color { + float r; + + float g; + + float b; + + float a; + + u8 to_r8() const { + return static_cast(round32(clamp(this->r, 0.0f, 1.0f) * u8_max)); + } + + u8 to_g8() const { + return static_cast(round32(clamp(this->g, 0.0f, 1.0f) * u8_max)); + } + + u8 to_b8() const { + return static_cast(round32(clamp(this->b, 0.0f, 1.0f) * u8_max)); + } + + u8 to_a8() const { + return static_cast(round32(clamp(this->a, 0.0f, 1.0f) * u8_max)); + } + }; +} diff --git a/source/core/math.cpp b/source/core/math.cpp new file mode 100644 index 0000000..1cc49b5 --- /dev/null +++ b/source/core/math.cpp @@ -0,0 +1,19 @@ +export module core.math; + +import core; + +export namespace core { + struct vector2 { + core::f32 x; + + core::f32 y; + }; + + struct vector3 { + core::f32 x; + + core::f32 y; + + core::f32 z; + }; +} diff --git a/source/core/sequence.cpp b/source/core/sequence.cpp new file mode 100644 index 0000000..bcc0868 --- /dev/null +++ b/source/core/sequence.cpp @@ -0,0 +1,118 @@ +export module core.sequence; + +import core; + +export namespace core { + enum class reserve_error { + out_of_memory, + }; + + template struct sequence { + sequence() = default; + + sequence(sequence const &) = delete; + + virtual ~sequence() {}; + + virtual slice as_slice() const = 0; + + virtual optional append(slice const & appended_elements) = 0; + }; + + template struct stack : public sequence { + stack(allocator * buffer_allocator) : filled{0}, + buffer{0}, buffer_allocator{buffer_allocator} { + + this->elements = this->buffer; + } + + ~stack() override { + if (this->elements.pointer != this->buffer) + this->buffer_allocator->deallocate(this->elements.pointer); + } + + slice as_slice() const override { + return this->elements; + } + + optional push(element const & pushed_element) { + if (this->filled == this->elements.length) { + optional const maybe_error = this->reserve(this->elements.length); + + if (maybe_error.has_value()) return maybe_error; + } + + this->elements[this->filled] = pushed_element; + this->filled += 1; + + return {}; + } + + optional append(slice const & pushed_elements) override { + usize const updated_fill = this->filled + pushed_elements.length; + + if (updated_fill >= this->elements.length) { + optional const maybe_error = this->reserve(max(this->elements.length, updated_fill)); + + if (maybe_error.has_value()) return maybe_error; + } + + for (usize i = 0; i < pushed_elements.length; i += 1) + this->elements[this->filled + i] = pushed_elements[i]; + + this->filled = updated_fill; + + return {}; + } + + optional reserve(usize capacity) { + usize const requested_capacity = this->elements.length + capacity; + + if (this->elements.pointer == this->buffer) { + u8 * const maybe_allocation = this->buffer_allocator-> + reallocate(nullptr, sizeof(element) * requested_capacity); + + if (maybe_allocation == nullptr) { + this->elements = {}; + + return reserve_error::out_of_memory; + } + + this->elements = {reinterpret_cast(maybe_allocation), requested_capacity}; + } else { + u8 * const maybe_allocation = this->buffer_allocator->reallocate( + reinterpret_cast(this->elements.pointer), + sizeof(element) * requested_capacity); + + if (maybe_allocation == nullptr) { + this->elements = {}; + + return reserve_error::out_of_memory; + } + + this->elements = {reinterpret_cast(maybe_allocation), requested_capacity}; + } + + return {}; + } + + private: + allocator * buffer_allocator; + + usize filled; + + slice elements; + + element buffer[initial_capacity]; + }; + + struct sequence_writer : public writable { + sequence_writer(sequence * target_sequence) : writable{[target_sequence](slice const & buffer) -> optional { + optional const maybe_error = target_sequence->append(buffer); + + if (maybe_error.has_value()) return {}; + + return buffer.length; + }} {} + }; +} diff --git a/source/kym.cpp b/source/kym.cpp new file mode 100644 index 0000000..3ac4528 --- /dev/null +++ b/source/kym.cpp @@ -0,0 +1,49 @@ +export module kym; + +import core; +import core.math; + +export namespace kym { + enum class value_type { + nil, + boolean, + integer, + scalar, + vector2, + vector3, + object, + }; + + struct value { + value_type type; + + union { + bool boolean; + + core::i64 integer; + + core::f64 scalar; + + core::vector2 vector2; + + core::vector3 vector3; + + void * object; + } as; + + core::optional as_u16() const { + if ((this->type == value_type::integer) && + (this->as.integer >= 0) && (this->as.integer <= core::u16_max)) { + + return static_cast(this->as.integer); + } + + return {}; + } + }; + + value nil = { + .type = value_type::nil, + .as = {.object = nullptr}, + }; +}; diff --git a/source/kym/environment.cpp b/source/kym/environment.cpp new file mode 100644 index 0000000..dfd3b7f --- /dev/null +++ b/source/kym/environment.cpp @@ -0,0 +1,144 @@ +export module kym.environment; + +import core; +import core.sequence; + +import kym; + +export namespace kym { + struct vm; +} + +enum class token_kind { + end, +}; + +struct token { + core::slice text; + + token_kind kind; +}; + +using tokenizable = core::callable; + +struct bytecode { + bytecode(core::allocator * allocator) : error_message_buffer{allocator} { + + } + + bool compile(tokenizable const & bytecode_tokenizable) { + for (;;) { + token const initial_token = bytecode_tokenizable(); + + switch (initial_token.kind) { + case token_kind::end: return true; + + default: core::unreachable(); + } + } + } + + kym::value execute(kym::vm & vm, core::slice const & arguments) { + return kym::nil; + } + + core::slice error_message() const { + return this->error_message_buffer.as_slice(); + } + + private: + core::stack error_message_buffer; +}; + +export namespace kym { + struct bound_object { + core::callable cleanup; + + core::callable)> call; + + value get_field(core::slice const & field_name) { + return nil; + } + + bool is_string() { + return false; + } + + core::usize stringify(core::writable const & writable) { + return 0; + } + }; + + struct vm { + struct init_options { + core::u16 datastack_size; + + core::u16 callstack_size; + }; + + vm(core::allocator * allocator, auto log) : allocator{allocator}, log{log}, data_stack{} {} + + ~vm() { + if (this->data_stack.pointer != nullptr) + this->allocator->deallocate(this->data_stack.pointer); + } + + bool init(init_options const & options) { + core::u8 * const data_stack_buffer = this->allocator->reallocate(reinterpret_cast + (this->data_stack.pointer), options.datastack_size * sizeof(value)); + + if (data_stack_buffer == nullptr) return false; + + this->data_stack = { + reinterpret_cast(data_stack_buffer), options.datastack_size}; + + return true; + } + + value compile(core::slice const & source) { + bytecode * source_bytecode = new (*this->allocator) bytecode{allocator}; + + if (source_bytecode == nullptr) return nil; + + core::usize cursor = 0; + + if (source_bytecode->compile([]() { + return token{ + + }; + })) { + this->log(source_bytecode->error_message()); + this->allocator->deallocate(source_bytecode); + + return nil; + } + + return this->new_object([this, source_bytecode](bound_object & object) { + object.cleanup = [this, source_bytecode]() { + this->allocator->deallocate(source_bytecode); + }; + + object.call = [this, source_bytecode](core::slice const & arguments) { + return source_bytecode->execute(*this, arguments); + }; + + this->allocator->deallocate(source_bytecode); + }); + } + + value new_object(core::callable const & then) { + return nil; + } + + void with_object(value object_value, core::callable const & then) { + + } + + private: + core::slice data_stack; + + core::callable)> log; + + core::allocator * allocator; + }; +} diff --git a/source/runtime.cpp b/source/runtime.cpp new file mode 100644 index 0000000..a183d25 --- /dev/null +++ b/source/runtime.cpp @@ -0,0 +1,81 @@ +export module runtime; + +import app; +import app.sdl; + +import core; +import core.math; +import core.sequence; + +import kym; +import kym.environment; + +extern "C" int main(int argc, char const * const * argv) { + return app::display("Ona Runtime", [](app::system & system, app::graphics & graphics) -> int { + constexpr app::path config_path = app::path::empty().joined("config.kym"); + bool is_config_loaded = false; + + system.bundle().read_file(config_path, [&](core::readable const & config_readable) { + kym::vm vm{&system.thread_safe_allocator(), [&system](core::slice const & error_message) { + system.log(app::log_level::error, error_message); + }}; + + if (!vm.init({ + .datastack_size = 64, + .callstack_size = 64, + })) { + system.log(app::log_level::error, "failed to allocate memory for config vm"); + + return; + } + + core::stack config_source{&system.thread_safe_allocator()}; + core::u8 config_source_stream_buffer[1024] = {}; + + if (!core::stream(core::sequence_writer{&config_source}, + config_readable, config_source_stream_buffer).has_value()) return; + + vm.with_object(vm.compile(config_source.as_slice().as_chars()), [&](kym::bound_object & config_script) { + vm.with_object(config_script.call({}), [&](kym::bound_object & config) { + core::u16 const width = config.get_field("width").as_u16().value_or(0); + + if (width == 0) return system.log( + app::log_level::error, "failed to decode `width` property of config"); + + core::u16 const height = config.get_field("height").as_u16().value_or(0); + + if (height == 0) return system.log( + app::log_level::error, "failed to decode `height` property of config"); + + graphics.show(width, height); + + vm.with_object(config.get_field("title"), [&](kym::bound_object & title) { + core::stack title_buffer{&system.thread_safe_allocator()}; + + if (!title.is_string()) return system.log( + app::log_level::error, "failed to decode `title` property of config"); + + title.stringify(core::sequence_writer(&title_buffer)); + + is_config_loaded = true; + }); + }); + }); + }); + + if (!is_config_loaded) { + system.log(app::log_level::error, "failed to load config"); + + return core::u8_max; + } + + // app::canvas canvas_2d(); + + while (system.poll()) { + // canvas_2d.render(graphics); + graphics.present(); + } + + return 0; + }); +} diff --git a/src/io.zig b/src/io.zig deleted file mode 100644 index 58a3c38..0000000 --- a/src/io.zig +++ /dev/null @@ -1,148 +0,0 @@ -const stack = @import("./stack.zig"); -const std = @import("std"); - -/// -/// Opaque interface to a "writable" resource, such as a block device, memory buffer, or network -/// socket. -/// -pub const Writer = struct { - context: *anyopaque, - writeContext: fn (*anyopaque, []const u8) usize, - - /// - /// Radices supported by [writeInt]. - /// - pub const Radix = enum { - binary, - tinary, - quaternary, - quinary, - senary, - septenary, - octal, - nonary, - decimal, - undecimal, - duodecimal, - tridecimal, - tetradecimal, - pentadecimal, - hexadecimal, - }; - - /// - /// Wraps and returns a reference to `write_context` of type `WriteContext` and its associated - /// `writeContext` writing operation in a [Writer]. - /// - pub fn wrap( - comptime WriteContext: type, - write_context: *WriteContext, - comptime writeContext: fn (*WriteContext, []const u8) usize - ) Writer { - return .{ - .context = write_context, - - .writeContext = struct { - fn write(context: *anyopaque, buffer: []const u8) usize { - return writeContext(@ptrCast(*WriteContext, - @alignCast(@alignOf(WriteContext), context)), buffer); - } - }.write, - }; - } - - /// - /// Attempts to write `buffer` to `writer`, returning the number of bytes from `buffer` that - /// were successfully written. - /// - pub fn write(writer: Writer, buffer: []const u8) usize { - return writer.writeContext(writer.context, buffer); - } - - /// - /// Writes the singular `byte` to `writer`, returning `true` if it was successfully written, - /// otherwise `false`. - /// - pub fn writeByte(writer: Writer, byte: u8) bool { - return (writer.writeContext(writer.context, - @ptrCast([*]const u8, &byte)[0 .. 1]) != 0); - } - - /// - /// Writes `value` as a ASCII / UTF-8 encoded integer to `writer`, returning `true` if the full - /// sequence was successfully written, otherwise `false`. - /// - /// The `radix` argument identifies which base system to encode `value` as, with `10` being - /// decimal, `16` being hexadecimal, `8` being octal`, so on and so forth. - /// - pub fn writeInt(writer: Writer, radix: Radix, value: anytype) bool { - const Int = @TypeOf(value); - const type_info = @typeInfo(Int); - - switch (type_info) { - .Int => { - if (value == 0) return writer.writeByte('0'); - - // TODO: Unhardcode this as it will break with large ints. - var buffer = std.mem.zeroes([28]u8); - var buffer_count = @as(usize, 0); - var n1 = value; - - if ((type_info.Int.signedness == .signed) and (value < 0)) { - // Negative value. - n1 = -value; - buffer[0] = '-'; - buffer_count += 1; - } - - while (n1 != 0) { - const base = @enumToInt(radix); - - buffer[buffer_count] = @intCast(u8, (n1 % base) + '0'); - n1 = (n1 / base); - buffer_count += 1; - } - - for (buffer[0 .. (buffer_count / 2)]) |_, i| - std.mem.swap(u8, &buffer[i], &buffer[buffer_count - i - 1]); - - return (writer.write(buffer[0 .. buffer_count]) == buffer_count); - }, - - // Cast comptime int into known-size integer and try again. - .ComptimeInt => return writer. - writeInt(radix, @intCast(std.math.IntFittingRange(value, value), value)), - - else => @compileError("value must be of type int"), - } - } -}; - -/// -/// Writer that silently throws consumed data away and never fails. -/// -/// This is commonly used for testing or redirected otherwise unwanted output data that can't not be -/// sent somewhere for whatever reason. -/// -pub const null_writer = Writer{ - .context = undefined, - - .writeContext = struct { - fn write(_: *anyopaque, buffer: []const u8) usize { - return buffer.len; - } - }.write, -}; - -test { - const testing = std.testing; - - { - const sequence = "foo"; - - try testing.expectEqual(null_writer.write(sequence), sequence.len); - } - - try testing.expect(null_writer.writeByte(0)); - try testing.expect(null_writer.writeInt(.decimal, 420)); -} diff --git a/src/main.zig b/src/main.zig deleted file mode 100644 index 4108ff8..0000000 --- a/src/main.zig +++ /dev/null @@ -1,50 +0,0 @@ -const ext = @cImport({ - @cInclude("SDL2/SDL.h"); -}); - -const io = @import("./io.zig"); -const stack = @import("./stack.zig"); -const std = @import("std"); -const sys = @import("./sys.zig"); - -/// -/// Entry point. -/// -pub fn main() anyerror!void { - return sys.runGraphics(anyerror, run); -} - -test { - _ = io; - _ = stack; - _ = std; - _ = sys; -} - -fn run(event_loop: *sys.EventLoop, graphics: *sys.GraphicsContext) anyerror!void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - - defer _ = gpa.deinit(); - - { - const file_access = try event_loop.open(.readonly, - try sys.FileSystem.data.joinedPath(&.{"data", "ona.lua"})); - - defer event_loop.close(file_access); - - const file_size = try file_access.size(event_loop); - const allocator = gpa.allocator(); - const buffer = try allocator.alloc(u8, file_size); - - defer allocator.free(buffer); - - if ((try event_loop.readFile(file_access, buffer)) != file_size) - return error.ScriptLoadFailure; - - event_loop.log(.debug, buffer); - } - - while (graphics.poll()) |_| { - graphics.present(); - } -} diff --git a/src/mem.zig b/src/mem.zig deleted file mode 100644 index 3df40b9..0000000 --- a/src/mem.zig +++ /dev/null @@ -1,87 +0,0 @@ -const std = @import("std"); - -/// -/// State machine for lazily computing all components of [Spliterator.source] that match the pattern -/// in [Spliterator.delimiter]. -/// -pub fn Spliterator(comptime Element: type) type { - return struct { - source: []const Element, - delimiter: []const Element, - - const Self = @This(); - - /// - /// Returns `true` if there is more data to be processed, otherwise `false`. - /// - pub fn hasNext(self: Self) bool { - return (self.source.len != 0); - } - - /// - /// Iterates on `self` and returns the next view of [Spliterator.source] that matches - /// [Spliterator.delimiter], or `null` if there is no more data to be processed. - /// - pub fn next(self: *Self) ?[]const Element { - if (!self.hasNext()) return null; - - if (std.mem.indexOfPos(Element, self.source, 0, self.delimiter)) |index| { - defer self.source = self.source[(index + self.delimiter.len) .. self.source.len]; - - return self.source[0 .. index]; - } - - defer self.source = self.source[self.source.len .. self.source.len]; - - return self.source; - } - }; -} - -test { - const testing = std.testing; - - // Single-character delimiter. - { - var spliterator = Spliterator(u8){ - .source = "single.character.separated.hello.world", - .delimiter = ".", - }; - - const components = [_][]const u8{"single", "character", "separated", "hello", "world"}; - var index = @as(usize, 0); - - while (spliterator.next()) |split| : (index += 1) { - try testing.expect(std.mem.eql(u8, split, components[index])); - } - } - - // Multi-character delimiter. - { - var spliterator = Spliterator(u8){ - .source = "finding a needle in a needle stack", - .delimiter = "needle", - }; - - const components = [_][]const u8{"finding a ", " in a ", " stack"}; - var index = @as(usize, 0); - - while (spliterator.next()) |split| : (index += 1) { - try testing.expect(std.mem.eql(u8, split, components[index])); - } - } -} - -/// -/// Searches the slice of `Data` referenced by `data` for the first instance of `sought_datum`, -/// returning its index or `null` if it could not be found. -/// -pub fn findFirst(comptime Data: type, data: []const Data, sought_datum: Data) ?usize { - for (data) |datum, index| if (datum == sought_datum) return index; - - return null; -} - -test { - try std.testing.expectEqual(findFirst(u8, "1234567890", '7'), 6); -} diff --git a/src/stack.zig b/src/stack.zig deleted file mode 100755 index 3f8284a..0000000 --- a/src/stack.zig +++ /dev/null @@ -1,117 +0,0 @@ -const io = @import("./io.zig"); -const std = @import("std"); - -pub fn Fixed(comptime Element: type) type { - return struct { - filled: usize = 0, - buffer: []Element, - - const Self = @This(); - - /// - /// Wraps `self` and returns it in a [io.Writer] value. - /// - /// Note that this will raise a compilation error if [Element] is not `u8`. - /// - pub fn writer(self: *Self) io.Writer { - if (Element != u8) @compileError("Cannot coerce fixed stack of type " ++ - @typeName(Element) ++ " into a Writer"); - - return io.Writer.wrap(Self, self, struct { - fn write(stack: *Self, buffer: []const u8) usize { - stack.pushAll(buffer) catch |err| switch (err) { - error.Overflow => return 0, - }; - - return buffer.len; - } - }.write); - } - - /// - /// Clears all elements from `self`. - /// - pub fn clear(self: *Self) void { - self.filled = 0; - } - - /// - /// Counts and returns the number of pushed elements in `self`. - /// - pub fn count(self: Self) usize { - return self.filled; - } - - /// - /// Attempts to pop the tail-end of `self`, returning the element value or `null` if the - /// stack is empty. - /// - pub fn pop(self: *Self) ?Element { - if (self.filled == 0) return null; - - self.filled -= 1; - - return self.buffer[self.filled]; - } - - /// - /// Attempts to push `element` into `self`, returning a [FixedPushError] if it failed. - /// - pub fn push(self: *Self, element: Element) FixedPushError!void { - if (self.filled == self.buffer.len) return error.Overflow; - - self.buffer[self.filled] = element; - self.filled += 1; - } - - /// - /// Attempts to push all of `elements` into `self`, returning a [FixedPushError] if it - /// failed. - /// - pub fn pushAll(self: *Self, elements: []const u8) FixedPushError!void { - const filled = (self.filled + elements.len); - - if (filled > self.buffer.len) return error.Overflow; - - std.mem.copy(u8, self.buffer[self.filled ..], elements); - - self.filled = filled; - } - }; -} - -/// -/// Potential errors that may occur while trying to push one or more elements into a stack of a -/// known maximum size. -/// -/// [FinitePushError.Overflow] is returned if the stack does not have sufficient capacity to hold a -/// given set of elements. -/// -pub const FixedPushError = error { - Overflow, -}; - -test { - const testing = std.testing; - var buffer = std.mem.zeroes([4]u8); - var stack = Fixed(u8){.buffer = &buffer}; - - try testing.expectEqual(stack.count(), 0); - try testing.expectEqual(stack.pop(), null); - try stack.push(69); - try testing.expectEqual(stack.count(), 1); - try testing.expectEqual(stack.pop(), 69); - try stack.pushAll(&.{42, 10, 95, 0}); - try testing.expectEqual(stack.count(), 4); - try testing.expectError(FixedPushError.Overflow, stack.push(1)); - try testing.expectError(FixedPushError.Overflow, stack.pushAll(&.{1, 11, 11})); - - stack.clear(); - - try testing.expectEqual(stack.count(), 0); - - const writer = stack.writer(); - - try testing.expectEqual(writer.write(&.{0, 0, 0, 0}), 4); - try testing.expectEqual(writer.writeByte(0), false); -} diff --git a/src/sys.zig b/src/sys.zig deleted file mode 100644 index b880fef..0000000 --- a/src/sys.zig +++ /dev/null @@ -1,642 +0,0 @@ -const ext = @cImport({ - @cInclude("SDL2/SDL.h"); -}); - -const io = @import("./io.zig"); -const mem = @import("./mem.zig"); -const stack = @import("./stack.zig"); -const std = @import("std"); - -/// -/// A thread-safe platform abstraction over multiplexing system I/O processing and event handling. -/// -pub const EventLoop = opaque { - /// - /// Linked list of messages chained together to be processed by the internal file system message - /// processor of an [EventLoop]. - /// - const FileSystemMessage = struct { - next: ?*FileSystemMessage = null, - frame: anyframe, - - request: union(enum) { - exit, - - close: struct { - file_access: *FileAccess, - }, - - log: struct { - message: []const u8, - kind: LogKind, - }, - - open: struct { - mode: OpenMode, - file_system_path: *const FileSystem.Path, - result: OpenError!*FileAccess = error.NotFound, - }, - - read_file: struct { - file_access: *FileAccess, - buffer: []const u8, - result: FileError!usize = error.Inaccessible, - }, - - seek_file: struct { - file_access: *FileAccess, - origin: SeekOrigin, - offset: usize, - result: FileError!void = error.Inaccessible, - }, - - tell_file: struct { - file_access: *FileAccess, - result: FileError!usize = error.Inaccessible, - }, - }, - }; - - /// - /// Internal state of the event loop hidden from the API consumer. - /// - const Implementation = struct { - user_prefix: []const u8, - file_system_semaphore: *ext.SDL_sem, - file_system_mutex: *ext.SDL_mutex, - file_system_thread: *ext.SDL_Thread, - file_system_messages: ?*FileSystemMessage = null, - - /// - /// Casts `event_loop` to a [Implementation] reference. - /// - /// *Note* that if `event_loop` does not have the same alignment as [Implementation], - /// safety-checked undefined behavior will occur. - /// - fn cast(event_loop: *EventLoop) *Implementation { - return @ptrCast(*Implementation, @alignCast(@alignOf(Implementation), event_loop)); - } - }; - - /// - /// [LogKind.info] represents a log message which is purely informative and does not indicate - /// any kind of issue. - /// - /// [LogKind.debug] represents a log message which is purely for debugging purposes and will - /// only occurs in debug builds. - /// - /// [LogKind.warning] represents a log message which is a warning about a issue that does not - /// break anything important but is not ideal. - /// - pub const LogKind = enum(c_int) { - info = ext.SDL_LOG_PRIORITY_INFO, - debug = ext.SDL_LOG_PRIORITY_DEBUG, - warning = ext.SDL_LOG_PRIORITY_WARN, - }; - - /// - /// [OpenError.NotFound] is a catch-all for when a file could not be located to be opened. This - /// may be as simple as it doesn't exist or the because the underlying file-system will not / - /// cannot give access to it at this time. - /// - pub const OpenError = error { - NotFound, - }; - - /// - /// [OpenMode.readonly] indicates that an existing file is opened in a read-only state, - /// disallowing write access. - /// - /// [OpenMode.overwrite] indicates that an empty file has been created or an existing file has - /// been completely overwritten into. - /// - /// [OpenMode.append] indicates that an existing file that has been opened for reading from and - /// writing to on the end of existing data. - /// - pub const OpenMode = enum { - readonly, - overwrite, - append, - }; - - /// - /// [SeekOrigin.head] indicates that a seek operation will seek from the offset origin of the - /// file beginning, or "head". - /// - /// [SeekOrigin.tail] indicates that a seek operation will seek from the offset origin of the - /// file end, or "tail". - /// - /// [SeekOrigin.cursor] indicates that a seek operation will seek from the current position of - /// the file cursor. - /// - pub const SeekOrigin = enum { - head, - tail, - cursor, - }; - - /// - /// Closes access to the file referenced by `file_access` via `event_loop`. - /// - /// *Note* that nothing happens to `file_access` if it is already closed. - /// - pub fn close(event_loop: *EventLoop, file_access: *FileAccess) void { - var file_system_message = FileSystemMessage{ - .frame = @frame(), - .request = .{.close = .{.file_access = file_access}}, - }; - - suspend event_loop.enqueueFileSystemMessage(&file_system_message); - } - - /// - /// Enqueues `message` to the file system message processor to be processed at a later, non- - /// deterministic point. - /// - fn enqueueFileSystemMessage(event_loop: *EventLoop, message: *FileSystemMessage) void { - const implementation = Implementation.cast(event_loop); - - // TODO: Error check this. - _ = ext.SDL_LockMutex(implementation.file_system_mutex); - - if (implementation.file_system_messages) |messages| { - messages.next = message; - } else { - implementation.file_system_messages = message; - } - - // TODO: Error check these. - _ = ext.SDL_UnlockMutex(implementation.file_system_mutex); - _ = ext.SDL_SemPost(implementation.file_system_semaphore); - } - - /// - /// Writes `message` to the application log with `kind` via `event_loop`. - /// - /// *Note* that `message` is not guaranteed to be partly, wholely, or at all written. - /// - pub fn log(event_loop: *EventLoop, kind: LogKind, message: []const u8) void { - var file_system_message = FileSystemMessage{ - .frame = @frame(), - - .request = .{.log = .{ - .message = message, - .kind = kind, - }}, - }; - - suspend event_loop.enqueueFileSystemMessage(&file_system_message); - } - - /// - /// Attempts to open access to a file referenced at `file_system_path` using `mode` as the way - /// to open it via `event_loop`. - /// - /// A [FileAccess] pointer is returned referencing the opened file or a [OpenError] if the file - /// could not be opened. - /// - /// *Note* that all files are opened in "binary-mode", or Unix-mode. There are no conversions - /// applied when data is accessed from a file. - /// - pub fn open(event_loop: *EventLoop, mode: OpenMode, - file_system_path: FileSystem.Path) OpenError!*FileAccess { - - var file_system_message = FileSystemMessage{ - .frame = @frame(), - - .request = .{.open = .{ - .mode = mode, - .file_system_path = &file_system_path, - }}, - }; - - suspend event_loop.enqueueFileSystemMessage(&file_system_message); - - return file_system_message.request.open.result; - } - - /// - /// [FileSystemMessage] processing function used by a dedicated worker thread, where `data` is - /// a type-erased reference to a [EventLoop]. - /// - /// The processor returns `0` if it exited normally or any other value if an erroneous exit - /// occured. - /// - fn processFileSystemMessages(data: ?*anyopaque) callconv(.C) c_int { - const implementation = Implementation.cast(@ptrCast(*EventLoop, data orelse unreachable)); - - while (true) { - while (implementation.file_system_messages) |messages| { - switch (messages.request) { - .exit => return 0, - - .log => |*log_request| ext.SDL_LogMessage(ext.SDL_LOG_CATEGORY_APPLICATION, - @enumToInt(log_request.priority), log_request.message), - - .open => |*open_request| { - switch (open_request.path.file_system) { - .data => { - // TODO: Implement - open_request.result = error.NotFound; - }, - - .user => { - var path_buffer = std.mem.zeroes([4096]u8); - var path = stack.Fixed(u8){.buffer = path_buffer[0 .. ]}; - - path.pushAll(implementation.user_prefix) catch { - open_request.result = error.BadFileSystem; - - continue; - }; - - if (!open_request.path.write(path.writer())) { - open_request.result = error.NotFound; - - continue; - } - - if (ext.SDL_RWFromFile(&path_buffer, switch (open_request.mode) { - .readonly => "rb", - .overwrite => "wb", - .append => "ab", - })) |rw_ops| { - open_request.result = @ptrCast(*FileAccess, rw_ops); - } else { - open_request.result = error.NotFound; - } - }, - } - }, - - .close => |*close_request| { - // TODO: Use this result somehow. - _ = ext.SDL_RWclose(@ptrCast(*ext.SDL_RWops, @alignCast( - @alignOf(ext.SDL_RWops), close_request.file_access))); - }, - - .read_file => |read_request| { - // TODO: Implement. - _ = read_request; - }, - - .seek_file => |seek_request| { - // TODO: Implement. - _ = seek_request; - }, - - .tell_file => |tell_request| { - // TODO: Implement. - _ = tell_request; - }, - } - - resume messages.frame; - - implementation.file_system_messages = messages.next; - } - - // TODO: Error check this. - _ = ext.SDL_SemWait(implementation.file_system_semaphore); - } - } - - /// - /// Attempts to read the contents of the file referenced by `file_access` at the current file - /// cursor position into `buffer`. - /// - /// The number of bytes that could be read / fitted into `buffer` is returned or a [FileError] - /// if the file failed to be read. - /// - pub fn readFile(event_loop: *EventLoop, file_access: *FileAccess, - buffer: []const u8) FileError!usize { - - var file_system_message = FileSystemMessage{ - .frame = @frame(), - - .request = .{.read_file = .{ - .file_access = file_access, - .buffer = buffer, - }}, - }; - - suspend event_loop.enqueueFileSystemMessage(&file_system_message); - - return file_system_message.request.read_file.result; - } - - /// - /// Attempts to tell the current file cursor position for the file referenced by `file_access`. - /// - /// Returns the number of bytes into the file that the cursor is relative to its beginning or a - /// [FileError] if the file failed to be queried. - /// - pub fn queryFile(event_loop: *EventLoop, file_access: *FileAccess) FileError!usize { - var file_system_message = FileSystemMessage{ - .frame = @frame(), - .request = .{.tell_file = .{.file_access = file_access}}, - }; - - suspend event_loop.enqueueFileSystemMessage(&file_system_message); - - return file_system_message.request.tell_file.result; - } - - /// - /// Attempts to seek the file cursor through the file referenced by `file_access` from `origin` - /// to `offset` via `event_loop`, returning a [FileError] if the file failed to be sought. - /// - pub fn seekFile(event_loop: *EventLoop, file_access: *FileAccess, - origin: SeekOrigin, offset: usize) FileError!void { - - var file_system_message = FileSystemMessage{ - .frame = @frame(), - - .request = .{ - .seek_file = .{ - .file_access = file_access, - .origin = origin, - .offset = offset, - }, - }, - }; - - suspend event_loop.enqueueFileSystemMessage(&file_system_message); - - return file_system_message.request.seek_file.result; - } -}; - -/// -/// File-system agnostic abstraction for manipulating a file. -/// -pub const FileAccess = opaque { - /// - /// Scans the number of bytes in the file referenced by `file_access` via `event_loop`, returing - /// its byte size or a [FileError] if it failed. - /// - pub fn size(file_access: *FileAccess, event_loop: *EventLoop) FileError!usize { - // Save cursor to return to it later. - const origin_cursor = try event_loop.queryFile(file_access); - - try event_loop.seekFile(file_access, .tail, 0); - - const ending_cursor = try event_loop.queryFile(file_access); - - // Return to original cursor. - try event_loop.seekFile(file_access, .head, origin_cursor); - - return ending_cursor; - } -}; - -/// -/// With files typically being backed by a block device, they can produce a variety of errors - -/// from physical to virtual errors - these are all encapsulated by the API as general -/// [Error.Inaccessible] errors. -/// -pub const FileError = error { - Inaccessible, -}; - -/// -/// Platform-agnostic mechanism for working with an abstraction of the underlying file-system(s) -/// available to the application in a sandboxed environment. -/// -pub const FileSystem = enum { - data, - user, - - /// - /// Platform-agnostic mechanism for referencing files and directories on a [FileSystem]. - /// - pub const Path = struct { - file_system: FileSystem, - length: u16, - buffer: [max]u8, - - /// - /// Returns `true` if the length of `path` is empty, otherwise `false`. - /// - pub fn isEmpty(path: Path) bool { - return (path.length == 0); - } - - /// - /// Returns `true` if `this` is equal to `that`, otherwise `false`. - /// - pub fn equals(this: Path, that: Path) bool { - return std.mem.eql(u8, this.buffer[0 .. this.length], that.buffer[0 .. that.length]); - } - - /// - /// The maximum possible byte-length of a [Path]. - /// - /// Note that paths are encoded using UTF-8, meaning that a character may be bigger than one - /// byte. Because of this, it is not safe to asume that a path may hold [max] individual - /// characters. - /// - pub const max = 1000; - - /// - /// - /// - pub fn write(path: Path, writer: io.Writer) bool { - return (writer.write(path.buffer[0 .. path.length]) == path.length); - } - }; - - /// - /// [PathError.TooLong] occurs when creating a path that is greater than the maximum size **in - /// bytes**. - /// - pub const PathError = error { - TooLong, - }; - - /// - /// Creates and returns a [Path] value in the file system to the location specified by the - /// joining of the `sequences` path values. - /// - pub fn joinedPath(file_system: FileSystem, sequences: []const []const u8) PathError!Path { - var path = Path{ - .file_system = file_system, - .buffer = std.mem.zeroes([Path.max]u8), - .length = 0, - }; - - for (sequences) |sequence| if (sequence.len != 0) { - var components = mem.Spliterator(u8){ - .source = sequence, - .delimiter = "/", - }; - - while (components.next()) |component| if (component.len != 0) { - for (component) |byte| { - if (path.length == Path.max) return error.TooLong; - - path.buffer[path.length] = byte; - path.length += 1; - } - - if (path.length == Path.max) return error.TooLong; - - path.buffer[path.length] = '/'; - path.length += 1; - }; - }; - - return path; - } -}; - -/// -/// -/// -pub const GraphicsContext = opaque { - /// - /// - /// - pub const Event = struct { - keys_up: Keys = std.mem.zeroes(Keys), - keys_down: Keys = std.mem.zeroes(Keys), - keys_held: Keys = std.mem.zeroes(Keys), - - const Keys = [256]bool; - }; - - const Implementation = struct { - event: Event, - }; - - /// - /// - /// - pub fn poll(graphics_context: *GraphicsContext) ?*const Event { - _ = graphics_context; - - return null; - } - - /// - /// - /// - pub fn present(graphics_context: *GraphicsContext) void { - // TODO: Implement; - _ = graphics_context; - } -}; - -/// -/// -/// -pub fn GraphicsRunner(comptime Errors: type) type { - return fn (*EventLoop, *GraphicsContext) Errors!void; -} - -/// -/// -/// -pub fn runGraphics(comptime Errors: anytype, run: GraphicsRunner(Errors)) Errors!void { - if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize runtime"); - - return error.InitFailure; - } - - defer ext.SDL_Quit(); - - const pref_path = create_pref_path: { - const path = ext.SDL_GetPrefPath("ona", "ona") orelse { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to load user path"); - - return error.InitFailure; - }; - - break: create_pref_path path[0 .. std.mem.len(path)]; - }; - - defer ext.SDL_free(pref_path.ptr); - - const window = create_window: { - const pos = ext.SDL_WINDOWPOS_UNDEFINED; - var flags = @as(u32, 0); - - break: create_window ext.SDL_CreateWindow("Ona", pos, pos, 640, 480, flags) orelse { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create window"); - - return error.InitFailure; - }; - }; - - defer ext.SDL_DestroyWindow(window); - - const renderer = create_renderer: { - var flags = @as(u32, 0); - - break: create_renderer ext.SDL_CreateRenderer(window, -1, flags) orelse { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, "Failed to create renderer"); - - return error.InitFailure; - }; - }; - - defer ext.SDL_DestroyRenderer(renderer); - - var event_loop = EventLoop.Implementation{ - .file_system_semaphore = ext.SDL_CreateSemaphore(0) orelse { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, - "Failed to create file-system work scheduler"); - - return error.InitFailure; - }, - - .file_system_mutex = ext.SDL_CreateMutex() orelse { - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, - "Failed to create file-system work lock"); - - return error.InitFailure; - }, - - .file_system_thread = unreachable, - .user_prefix = pref_path, - }; - - event_loop.file_system_thread = ext.SDL_CreateThread( - EventLoop.processFileSystemMessages, "File System Worker", &event_loop) orelse { - - ext.SDL_LogCritical(ext.SDL_LOG_CATEGORY_APPLICATION, - "Failed to create file-system work processor"); - - return error.InitFailure; - }; - - defer { - ext.SDL_DestroyThread(event_loop.file_system_thread); - ext.SDL_DestroySemaphore(event_loop.file_system_mutex); - ext.SDL_DestroySemaphore(event_loop.file_system_semaphore); - } - - var graphics_context = GraphicsContext.Implementation{ - .event = .{ - - }, - }; - - var message = EventLoop.FileSystemMessage{ - .frame = @frame(), - .request = .exit, - }; - - @ptrCast(*EventLoop, event_loop).enqueueFileSystemMessage(&message); - - var status = @as(c_int, 0); - - ext.SDL_WaitThread(event_loop.file_system_thread, &status); - - if (status != 0) { - // TODO: Error check this. - } - - return run(@ptrCast(*EventLoop, &event_loop), @ptrCast(*GraphicsContext, &graphics_context)); -} From a9e0588878d7f05b47a8ab2db464795c58a76b3d Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 18 Feb 2023 03:38:22 +0000 Subject: [PATCH 02/35] Fix build script not compiling with debug symbols --- build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.py b/build.py index 41c3cb1..93acd4e 100755 --- a/build.py +++ b/build.py @@ -9,7 +9,7 @@ cache_path = "./cache/" if not(os.path.exists(cache_path)): os.mkdir(cache_path) -compile_command = f"clang++ -std=c++20 -fno-exceptions -fmodules -fprebuilt-module-path=./cache" +compile_command = f"clang++ -g -std=c++20 -fno-exceptions -fmodules -fprebuilt-module-path=./cache" object_file_paths = [] def compile_module(source_file_path, module_identifier) -> None: From 43383058c6734c9b94a82500c9d7442a68e7b8af Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 18 Feb 2023 13:19:01 +0000 Subject: [PATCH 03/35] Improve error handling with core::expected --- source/core.cpp | 96 ++++++++++++++++++++++++++++---------- source/core/sequence.cpp | 4 +- source/kym/environment.cpp | 89 +++++++++++++++++++++++++---------- source/runtime.cpp | 18 +++---- 4 files changed, 148 insertions(+), 59 deletions(-) diff --git a/source/core.cpp b/source/core.cpp index 429717e..af90811 100644 --- a/source/core.cpp +++ b/source/core.cpp @@ -70,6 +70,22 @@ export namespace core { __builtin_unreachable(); } + template constexpr scalar max(scalar const & a, scalar const & b) { + return (a > b) ? a : b; + } + + template constexpr scalar min(scalar const & a, scalar const & b) { + return (a < b) ? a : b; + } + + template constexpr scalar clamp(scalar const & value, scalar const & min_value, scalar const & max_value) { + return max(min_value, min(max_value, value)); + } + + f32 round32(f32 value) { + return __builtin_roundf(value); + } + template struct slice { usize length; @@ -167,6 +183,50 @@ export namespace core { u8 buffer[sizeof(element) + 1]; }; + template struct [[nodiscard]] expected { + expected(value_element const & value) : buffer{0} { + (*reinterpret_cast(this->buffer)) = value; + this->buffer[buffer_size] = 1; + } + + expected(error_element const & error) : buffer{0} { + (*reinterpret_cast(this->buffer)) = error; + } + + bool is_ok() const { + return this->buffer[buffer_size]; + } + + value_element & value() { + if (!this->is_ok()) unreachable(); + + return *reinterpret_cast(this->buffer); + } + + value_element const & value() const { + if (!this->is_ok()) unreachable(); + + return *reinterpret_cast(this->buffer); + } + + error_element & error() { + if (this->is_ok()) unreachable(); + + return *reinterpret_cast(this->buffer); + } + + error_element const & error() const { + if (this->is_ok()) unreachable(); + + return *reinterpret_cast(this->buffer); + } + + private: + static constexpr usize buffer_size = max(sizeof(value_element), sizeof(error_element)); + + u8 buffer[buffer_size + 1]; + }; + template struct callable; template struct callable { @@ -218,9 +278,13 @@ export namespace core { return a; } - using readable = callable(slice const &)>; + enum class io_error { + unavailable, + }; - using writable = callable(slice const &)>; + using readable = callable(slice const &)>; + + using writable = callable(slice const &)>; template bool equals(slice const & a, slice const & b) { if (a.length != b.length) return false; @@ -230,43 +294,27 @@ export namespace core { return true; } - optional stream(writable const & output, readable const & input, slice const & buffer) { + expected stream(writable const & output, readable const & input, slice const & buffer) { usize written = 0; - optional maybe_read = input(buffer); + expected maybe_read = input(buffer); - if (!maybe_read.has_value()) return {}; + if (!maybe_read.is_ok()) return maybe_read.error(); usize read = maybe_read.value(); while (read != 0) { - optional const maybe_written = output(buffer.until(read)); + expected const maybe_written = output(buffer.until(read)); - if (!maybe_written.has_value()) return {}; + if (!maybe_written.is_ok()) return maybe_read.error(); written += maybe_written.value(); maybe_read = input(buffer); - if (!maybe_read.has_value()) return {}; + if (!maybe_read.is_ok()) return maybe_read.error(); read = maybe_read.value(); } return written; } - - template scalar max(scalar const & a, scalar const & b) { - return (a > b) ? a : b; - } - - template scalar min(scalar const & a, scalar const & b) { - return (a < b) ? a : b; - } - - template scalar clamp(scalar const & value, scalar const & min_value, scalar const & max_value) { - return max(min_value, min(max_value, value)); - } - - f32 round32(f32 value) { - return __builtin_roundf(value); - } } diff --git a/source/core/sequence.cpp b/source/core/sequence.cpp index bcc0868..79d0bf4 100644 --- a/source/core/sequence.cpp +++ b/source/core/sequence.cpp @@ -107,10 +107,10 @@ export namespace core { }; struct sequence_writer : public writable { - sequence_writer(sequence * target_sequence) : writable{[target_sequence](slice const & buffer) -> optional { + sequence_writer(sequence * target_sequence) : writable{[target_sequence](slice const & buffer) -> expected { optional const maybe_error = target_sequence->append(buffer); - if (maybe_error.has_value()) return {}; + if (maybe_error.has_value()) return io_error::unavailable; return buffer.length; }} {} diff --git a/source/kym/environment.cpp b/source/kym/environment.cpp index dfd3b7f..87b26a6 100644 --- a/source/kym/environment.cpp +++ b/source/kym/environment.cpp @@ -19,16 +19,32 @@ struct token { token_kind kind; }; -using tokenizable = core::callable; +struct tokenizer { + core::slice source; + + tokenizer(core::slice const & source) : source{source} {} + + token next() { + core::usize cursor = 0; + + while (cursor < source.length) { + + } + + return token{ + .kind = token_kind::end, + }; + } +}; struct bytecode { bytecode(core::allocator * allocator) : error_message_buffer{allocator} { } - bool compile(tokenizable const & bytecode_tokenizable) { + bool compile(tokenizer bytecode_tokenizer) { for (;;) { - token const initial_token = bytecode_tokenizable(); + token const initial_token = bytecode_tokenizer.next(); switch (initial_token.kind) { case token_kind::end: return true; @@ -38,7 +54,7 @@ struct bytecode { } } - kym::value execute(kym::vm & vm, core::slice const & arguments) { + kym::value execute(kym::vm & vm, core::slice const & arguments) { return kym::nil; } @@ -51,22 +67,49 @@ struct bytecode { }; export namespace kym { - struct bound_object { - core::callable cleanup; + value default_call(vm & owning_vm, void * userdata, core::slice const & arguments) { + return nil; + } - core::callable)> call; + core::expected default_stringify(vm & owning_vm, void * userdata, core::writable const & writable) { + return writable(core::slice("[object]").as_bytes()); + } + + struct bound_object { + void * userdata; + + struct { + void(*cleanup)(vm &, void *); + + value(*call)(vm &, void *, core::slice const &); + + core::expected(*stringify)(vm &, void *, core::writable const &); + } behavior; + + bound_object(vm * owning_vm) : userdata{nullptr}, owning_vm{owning_vm}, behavior{ + .cleanup = [](vm & owning_vm, void * userdata) {}, + .call = default_call, + .stringify = default_stringify, + } {} + + void cleanup() { + this->behavior.cleanup(*this->owning_vm, this->userdata); + } + + value call(core::slice const & arguments) { + return this->behavior.call(*this->owning_vm, this->userdata, arguments); + } + + core::expected stringify(core::writable const & writable) { + return this->behavior.stringify(*this->owning_vm, this->userdata, writable); + } value get_field(core::slice const & field_name) { return nil; } - bool is_string() { - return false; - } - - core::usize stringify(core::writable const & writable) { - return 0; - } + private: + vm * owning_vm; }; struct vm { @@ -100,13 +143,7 @@ export namespace kym { if (source_bytecode == nullptr) return nil; - core::usize cursor = 0; - - if (source_bytecode->compile([]() { - return token{ - - }; - })) { + if (source_bytecode->compile(tokenizer{source})) { this->log(source_bytecode->error_message()); this->allocator->deallocate(source_bytecode); @@ -114,12 +151,14 @@ export namespace kym { } return this->new_object([this, source_bytecode](bound_object & object) { - object.cleanup = [this, source_bytecode]() { - this->allocator->deallocate(source_bytecode); + object.userdata = source_bytecode; + + object.behavior.cleanup = [](vm & owning_vm, void * userdata) { + owning_vm.allocator->deallocate(userdata); }; - object.call = [this, source_bytecode](core::slice const & arguments) { - return source_bytecode->execute(*this, arguments); + object.behavior.call = [](vm & owning_vm, void * userdata, core::slice const & arguments) -> value { + return reinterpret_cast(userdata)->execute(owning_vm, arguments); }; this->allocator->deallocate(source_bytecode); diff --git a/source/runtime.cpp b/source/runtime.cpp index a183d25..38de729 100644 --- a/source/runtime.cpp +++ b/source/runtime.cpp @@ -33,29 +33,31 @@ extern "C" int main(int argc, char const * const * argv) { core::u8 config_source_stream_buffer[1024] = {}; if (!core::stream(core::sequence_writer{&config_source}, - config_readable, config_source_stream_buffer).has_value()) return; + config_readable, config_source_stream_buffer).is_ok()) return; vm.with_object(vm.compile(config_source.as_slice().as_chars()), [&](kym::bound_object & config_script) { vm.with_object(config_script.call({}), [&](kym::bound_object & config) { core::u16 const width = config.get_field("width").as_u16().value_or(0); - if (width == 0) return system.log( - app::log_level::error, "failed to decode `width` property of config"); + if (width == 0) return system.log(app::log_level::error, + "failed to decode `width` property of config"); core::u16 const height = config.get_field("height").as_u16().value_or(0); - if (height == 0) return system.log( - app::log_level::error, "failed to decode `height` property of config"); + if (height == 0) return system.log(app::log_level::error, + "failed to decode `height` property of config"); graphics.show(width, height); vm.with_object(config.get_field("title"), [&](kym::bound_object & title) { core::stack title_buffer{&system.thread_safe_allocator()}; - if (!title.is_string()) return system.log( - app::log_level::error, "failed to decode `title` property of config"); + if (!title.stringify(core::sequence_writer(&title_buffer)).is_ok()) { + system.log(app::log_level::error, + "failed to decode `title` property of config"); - title.stringify(core::sequence_writer(&title_buffer)); + return; + } is_config_loaded = true; }); From 1261ee7e6049adbf0567d7d380851f4aed5b877b Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 18 Feb 2023 13:34:20 +0000 Subject: [PATCH 04/35] Update build instructions --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index ce4dcbb..4606782 100644 --- a/readme.md +++ b/readme.md @@ -41,7 +41,7 @@ Ona is also the Catalan word for "wave". Ona currently depends the following third-party tools to build it: - * GNU C++ compiler with full C++20 support or above. + * Clang / LLVM toolchain with full C++20 support or above. * Python interpreter version 3.10 or above. Additionally, Ona depends on the following third-party system-wide dependencies: From e3c4ab65f122d664acdfa89561a62b98445f21b8 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 18 Feb 2023 19:40:12 +0000 Subject: [PATCH 05/35] Clean up core library. --- source/app.cpp | 6 +- source/app/sdl.cpp | 17 +- source/core.cpp | 473 +++++++++++++++++++++++++++---------- source/core/image.cpp | 27 +++ source/core/lalgebra.cpp | 40 ++++ source/core/math.cpp | 19 -- source/core/sequence.cpp | 228 ++++++++++++------ source/kym.cpp | 2 +- source/kym/environment.cpp | 23 +- source/runtime.cpp | 16 +- 10 files changed, 608 insertions(+), 243 deletions(-) create mode 100644 source/core/lalgebra.cpp delete mode 100644 source/core/math.cpp diff --git a/source/app.cpp b/source/app.cpp index 5a4a1b3..84a3f56 100644 --- a/source/app.cpp +++ b/source/app.cpp @@ -2,7 +2,7 @@ export module app; import core; import core.image; -import core.math; +import core.lalgebra; export namespace app { struct path { @@ -32,8 +32,8 @@ export namespace app { return 0; } - bool equals(path const & that) { - return core::equals(this->as_slice(), that.as_slice()); + bool equals(path const & that) const { + return core::equals(this->as_slice().as_bytes(), that.as_slice().as_bytes()); } constexpr path joined(core::slice const & text) const { diff --git a/source/app/sdl.cpp b/source/app/sdl.cpp index ce1825c..c332c6b 100644 --- a/source/app/sdl.cpp +++ b/source/app/sdl.cpp @@ -9,19 +9,24 @@ import app; import core; import core.image; import core.sequence; -import core.math; +import core.lalgebra; struct bundled_file_store : public app::file_store { void read_file(app::path const & file_path, core::callable const & then) override { - // Path is guaranteed to never be greater than 512 characters long (file_path is max 256 and prefix is 2). - core::stack path_buffer{&core::null_allocator()}; + constexpr core::slice path_prefix = "./"; + constexpr core::usize path_max = 512; - if (path_buffer.append("./").has_value()) core::unreachable(); + if ((file_path.size() + path_prefix.length) > path_max) core::unreachable(); + + core::stack path_buffer{&core::null_allocator()}; + + if (path_buffer.append("./") != core::append_result::ok) core::unreachable(); // File path is guaranteed to be null-terminated. - if (path_buffer.append(file_path.as_slice()).has_value()) core::unreachable(); + if (path_buffer.append(file_path.as_slice()) != core::append_result::ok) + core::unreachable(); - SDL_RWops * rw_ops = ::SDL_RWFromFile(path_buffer.as_slice().pointer, "r"); + SDL_RWops * rw_ops = ::SDL_RWFromFile(path_buffer.begin(), "r"); if (rw_ops == nullptr) return; diff --git a/source/core.cpp b/source/core.cpp index af90811..4edacce 100644 --- a/source/core.cpp +++ b/source/core.cpp @@ -5,6 +5,20 @@ module; export module core; +// Runtime utilities. +export namespace core { + /** + * Triggers safety-checked behavior in debug mode. + * + * In release mode, the compiler can use this function as a marker to optimize out safety- + * checked logic branches that should never be executed. + */ + [[noreturn]] void unreachable() { + __builtin_unreachable(); + } +} + +// Concrete and interface types. export namespace core { using usize = size_t; @@ -36,118 +50,230 @@ export namespace core { using f64 = double; + /** + * Base type for runtime-pluggable memory allocation strategies used by the core library. + */ struct allocator { - allocator() = default; - - allocator(allocator const &) = delete; - virtual ~allocator() {}; - virtual u8 * reallocate(u8 * maybe_allocation, usize requested_size) = 0; + /** + * If `allocation` is `nullptr`, the allocator will attempt to allocate a new memory block + * of `requested_size` bytes. Otherwise, the allocator will attempt to reallocate + * `allocation` to be `request_size` bytes in size. + * + * The returned address will point to a dynamically allocated buffer of `requested_size` if + * the operation was successful, otherwise `nullptr`. + * + * *Note*: If the returned address is a non-`nullptr`, it should be deallocated prior to + * program exit. This may be achieved through either [deallocate] or implementation- + * specific allocator functionality. + * + * *Note*: Attempting to pass a non-`nullptr` `allocation` address not allocated by the + * allocator *will* result in erroneous implementation-behavior. + * + * *Note*: After invocation, `allocation` should be considered an invalid memory address. + */ + [[nodiscard]] virtual u8 * reallocate(u8 * allocation, usize requested_size) = 0; + /** + * If `allocation` points to a non-`nullptr` address, the allocator will deallocate it. + * Otherwise, the function has no side-effects. + * + * *Note* that attempting to pass a non-`nullptr` `allocation` address not allocated by the + * allocator *will* result in erroneous implementation-behavior. + */ virtual void deallocate(void * allocation) = 0; }; -} - -export void * operator new(core::usize requested_size, core::u8 * allocation_placement) { - return allocation_placement; -} - -export void * operator new[](core::usize requested_size, core::u8 * allocation_placement) { - return allocation_placement; -} - -export void * operator new(core::usize requested_size, core::allocator & allocator) { - return allocator.reallocate(nullptr, requested_size); -} - -export void * operator new[](core::usize requested_size, core::allocator & allocator) { - return allocator.reallocate(nullptr, requested_size); -} - -export namespace core { - [[noreturn]] void unreachable() { - __builtin_unreachable(); - } - - template constexpr scalar max(scalar const & a, scalar const & b) { - return (a > b) ? a : b; - } - - template constexpr scalar min(scalar const & a, scalar const & b) { - return (a < b) ? a : b; - } - - template constexpr scalar clamp(scalar const & value, scalar const & min_value, scalar const & max_value) { - return max(min_value, min(max_value, value)); - } - - f32 round32(f32 value) { - return __builtin_roundf(value); - } + /** + * Length-signed pointer type that describes how many elements of `type` it references, + * providing a type-safe wrapper for passing arrays and zero-terminated strings to functions. + */ template struct slice { + /** + * Number of `type` elements referenced. + */ usize length; + /** + * Base element address referenced. + */ type * pointer; - constexpr slice() : length{0}, pointer{nullptr} { - + constexpr slice() { + this->length = 0; + this->pointer = nullptr; } - constexpr slice(char const *&& zstring) : length{0}, pointer{zstring} { + constexpr slice(char const *&& zstring) { + this->pointer = zstring; + this->length = 0; + while (zstring[length] != 0) this->length += 1; } - constexpr slice(type * slice_pointer, usize slice_length) : length{slice_length}, pointer{slice_pointer} { - + constexpr slice(type * slice_pointer, usize slice_length) { + this->pointer = slice_pointer; + this->length = slice_length; } - template constexpr slice(type(&array)[array_size]) : length{array_size}, pointer{array} { - + constexpr slice(type * slice_begin, type * slice_end) { + this->pointer = slice_begin; + this->length = static_cast(slice_end - slice_begin); } + template constexpr slice(type(&array)[array_size]) { + this->pointer = array; + this->length = array_size; + } + + /** + * Reinterprets the data referenced as a series of bytes. + * + * The returned view is constant to protect against inadvertant memory corruption. + */ slice as_bytes() const { return {reinterpret_cast(this->pointer), this->length * sizeof(type)}; } + /** + * Reinterprets the data referenced as a series of chars. + * + * The returned view is constant to protect against inadvertant memory corruption. + * + * *Note* the returned value has no guarantees about the validity of any specific character + * encoding set. + */ slice as_chars() const { return {reinterpret_cast(this->pointer), this->length * sizeof(type)}; } + /** + * Returns the base pointer of the slice. + */ + constexpr type * begin() const { + return this->pointer; + } + + /** + * Returns the tail pointer of the slice. + */ + constexpr type * end() const { + return this->pointer + this->length; + } + + /** + * Returns a new slice with the base-pointer offset by `index` elements and a length of + * `range` elements from `index`. + * + * *Note* that attempting to slice with an `index` or `range` outside of the existing slice + * bounds will result in safety-checked behavior. + */ + constexpr slice sliced(usize index, usize range) const { + if ((this->length <= index) || ((range + index) > this->length)) unreachable(); + + return {this->pointer + index, range - index}; + } + operator slice() const { return (*reinterpret_cast const *>(this)); } - type & operator[](usize index) const { + constexpr type & operator[](usize index) const { + if (this->length <= index) core::unreachable(); + return this->pointer[index]; } - - slice after(usize index) const { - return {this->pointer + index, this->length - index}; - } - - slice until(usize index) const { - return {this->pointer, index}; - } - - slice between(usize a, usize b) const { - return {this->pointer + a, b}; - } - - constexpr type * begin() const { - return this->pointer; - } - - constexpr type * end() const { - return this->pointer + this->length; - } }; +} +// Math functions. +export namespace core { + /** + * Returns the maximum value between `a` and `b`. + */ + template constexpr scalar max(scalar const & a, scalar const & b) { + return (a > b) ? a : b; + } + + /** + * Returns the minimum value between `a` and `b`. + */ + template constexpr scalar min(scalar const & a, scalar const & b) { + return (a < b) ? a : b; + } + + /** + * Returns `value` clamped between the range of `min_value` and `max_value` (inclusive). + */ + template constexpr scalar clamp(scalar const & value, scalar const & min_value, scalar const & max_value) { + return max(min_value, min(max_value, value)); + } + + /** + * Returns `value` rounded to the nearest whole number. + */ + f32 round32(f32 value) { + return __builtin_roundf(value); + } +} + +/** + * Allocates and initializes a type of `requested_size` in `buffer`, returning its base pointer. As + * a result of accepting a pre-allocated buffer, invocation does not allocate any dynamic memory. + * + * *Note*: passing an `buffer` smaller than `requested_size` will result in safety-checked + * behavior. + */ +export void * operator new(core::usize requested_size, core::slice const & buffer) { + if (buffer.length < requested_size) core::unreachable(); + + return buffer.pointer; +} + +/** + * Allocates and initializes a series of types at `requested_size` in `buffer`, returning the base + * pointer. As a result of accepting a pre-allocated buffer, invocation does not allocate any + * dynamic memory. + * + * *Note*: passing an `buffer` smaller than `requested_size` will result in safety-checked + * behavior. + */ +export void * operator new[](core::usize requested_size, core::slice const & buffer) { + if (buffer.length < requested_size) core::unreachable(); + + return buffer.pointer; +} + +/** + * Attempts to allocate and initialize a type of `requested_size` using `allocator`. + * + * *Note*: If the returned address is a non-`nullptr`, it should be deallocated prior to program + * exit. This may be achieved through either [core::allocator::deallocate] or implementation- + * specific allocator functionality. + */ +export [[nodiscard]] void * operator new(core::usize requested_size, core::allocator & allocator) { + return allocator.reallocate(nullptr, requested_size); +} + +/** + * Attempts to allocate and initialize a series of types of `requested_size` using `allocator`. + * + * *Note*: If the returned address is a non-`nullptr`, it should be deallocated prior to program + * exit. This may be achieved through either [core::allocator::deallocate] or implementation- + * specific allocator functionality. + */ +export [[nodiscard]] void * operator new[](core::usize requested_size, core::allocator & allocator) { + return allocator.reallocate(nullptr, requested_size); +} + +// Wrapper types. +export namespace core { + /** + * Monadic container for a single-`element` value or nothing. + */ template struct [[nodiscard]] optional { - optional() : buffer{0} { - - } + optional() : buffer{0} {} optional(element const & value) : buffer{0} { (*reinterpret_cast(this->buffer)) = value; @@ -163,18 +289,38 @@ export namespace core { } } + /** + * Returns `true` if the optional contains a value, otherwise `false`. + */ bool has_value() const { return this->buffer[sizeof(element)] == 1; } + /** + * Returns the contained value or `fallback` if the optional is empty. + */ element const & value_or(element const & fallback) const { return this->has_value() ? *reinterpret_cast(this->buffer) : fallback; } + /** + * Returns a reference to the contained value. + * + * *Note*: attempting to access the value of an empty optional will trigger safety-checked + * behavior. + */ element & value() { + if (!this->has_value()) unreachable(); + return *reinterpret_cast(this->buffer); } + /** + * Returns the contained value. + * + * *Note*: attempting to access the value of an empty optional will trigger safety-checked + * behavior. + */ element const & value() const { return *reinterpret_cast(this->buffer); } @@ -183,6 +329,9 @@ export namespace core { u8 buffer[sizeof(element) + 1]; }; + /** + * Monadic container for a descriminating union of either `value_element` or `error_element`. + */ template struct [[nodiscard]] expected { expected(value_element const & value) : buffer{0} { (*reinterpret_cast(this->buffer)) = value; @@ -193,28 +342,55 @@ export namespace core { (*reinterpret_cast(this->buffer)) = error; } + /** + * Returns `true` if the optional contains a value, otherwise `false` if it holds an error. + */ bool is_ok() const { return this->buffer[buffer_size]; } + /** + * Returns a reference to the contained value. + * + * *Note*: attempting to access the value of an erroneous expected will trigger safety- + * checked behavior. + */ value_element & value() { if (!this->is_ok()) unreachable(); return *reinterpret_cast(this->buffer); } + /** + * Returns the contained value. + * + * *Note*: attempting to access the value of an erroneous expected will trigger safety- + * checked behavior. + */ value_element const & value() const { if (!this->is_ok()) unreachable(); return *reinterpret_cast(this->buffer); } + /** + * Returns a reference to the contained error. + * + * *Note*: attempting to access the error of a non-erroneous expected will trigger safety- + * checked behavior. + */ error_element & error() { if (this->is_ok()) unreachable(); return *reinterpret_cast(this->buffer); } + /** + * Returns the contained error. + * + * *Note*: attempting to access the error of a non-erroneous expected will trigger safety- + * checked behavior. + */ error_element const & error() const { if (this->is_ok()) unreachable(); @@ -229,39 +405,120 @@ export namespace core { template struct callable; - template struct callable { - using function = return_type(*)(argument_types...); + /** + * Type-erasing wrapper for functor types that have a call operator with a return value + * matching `return_value` and arguments matching `argument_values`. + */ + template struct callable { + using function = return_value(*)(argument_values...); + + callable(function callable_function) { + this->dispatcher = [](u8 const * userdata, argument_values... arguments) -> return_value { + return (*reinterpret_cast(userdata))(arguments...); + }; - callable(function callable_function) : dispatcher(dispatch_function) { new (this->capture) function{callable_function}; } callable(callable const &) = delete; - template callable(functor const & callable_functor) : dispatcher(dispatch_functor) { + template callable(functor const & callable_functor) { + this->dispatcher = [](u8 const * userdata, argument_values... arguments) -> return_value { + return (*reinterpret_cast(userdata))(arguments...); + }; + new (this->capture) functor{callable_functor}; } - return_type operator()(argument_types const &... arguments) const { + return_value operator()(argument_values const &... arguments) const { return this->dispatcher(this->capture, arguments...); } private: static constexpr usize capture_size = 24; - return_type(* dispatcher)(u8 const * userdata, argument_types... arguments); + return_value(* dispatcher)(u8 const * userdata, argument_values... arguments); u8 capture[capture_size]; - - static return_type dispatch_function(u8 const * userdata, argument_types... arguments) { - return (*reinterpret_cast(userdata))(arguments...); - } - - template static return_type dispatch_functor(u8 const * userdata, argument_types... arguments) { - return (*reinterpret_cast(userdata))(arguments...); - } }; + /** + * Errors that may occur while executing an opaque I/O operation via the `readable` and + * `writable` type aliases. + */ + enum class io_error { + unavailable, + }; + + using readable = callable(slice const &)>; + + using writable = callable(slice const &)>; +} + +// Input/output operations. +export namespace core { + /** + * Tests the equality of `a` against `b`, returning `true` if they contain identical bytes, + * otherwise `false`. + */ + bool equals(slice const & a, slice const & b) { + if (a.length != b.length) return false; + + for (size_t i = 0; i < a.length; i += 1) if (a[i] != b[i]) return false; + + return true; + } + + /** + * Copies the contents of `origin` into `target`. + * + * *Note*: safety-checked behavior is triggered if `target` is smaller than `origin`. + */ + void copy(slice const & target, slice const & origin) { + if (target.length < origin.length) core::unreachable(); + + for (usize i = 0; i < origin.length; i += 1) target[i] = origin[i]; + } + + /** + * 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(writable const & output, + readable const & input, slice const & buffer) { + + usize written = 0; + expected maybe_read = input(buffer); + + if (!maybe_read.is_ok()) return maybe_read.error(); + + usize read = maybe_read.value(); + + while (read != 0) { + expected const maybe_written = output(buffer.sliced(0, read)); + + if (!maybe_written.is_ok()) return maybe_read.error(); + + written += maybe_written.value(); + maybe_read = input(buffer); + + if (!maybe_read.is_ok()) return maybe_read.error(); + + read = maybe_read.value(); + } + + return 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 { @@ -277,44 +534,4 @@ export namespace core { return a; } - - enum class io_error { - unavailable, - }; - - using readable = callable(slice const &)>; - - using writable = callable(slice const &)>; - - template bool equals(slice const & a, slice const & b) { - if (a.length != b.length) return false; - - for (size_t i = 0; i < a.length; i += 1) if (a[i] != b[i]) return false; - - return true; - } - - expected stream(writable const & output, readable const & input, slice const & buffer) { - usize written = 0; - expected maybe_read = input(buffer); - - if (!maybe_read.is_ok()) return maybe_read.error(); - - usize read = maybe_read.value(); - - while (read != 0) { - expected const maybe_written = output(buffer.until(read)); - - if (!maybe_written.is_ok()) return maybe_read.error(); - - written += maybe_written.value(); - maybe_read = input(buffer); - - if (!maybe_read.is_ok()) return maybe_read.error(); - - read = maybe_read.value(); - } - - return written; - } } diff --git a/source/core/image.cpp b/source/core/image.cpp index 0a0a75f..5279c3f 100644 --- a/source/core/image.cpp +++ b/source/core/image.cpp @@ -3,27 +3,54 @@ export module core.image; import core; export namespace core { + /** + * All-purpose color value for red, green, blue, alpha channel-encoded values. + */ struct color { + /** + * Red channel. + */ float r; + /** + * Green channel. + */ float g; + /** + * Blue channel. + */ float b; + /** + * Alpha channel. + */ float a; + /** + * Red channel represented in an 8-bit unsigned value. + */ u8 to_r8() const { return static_cast(round32(clamp(this->r, 0.0f, 1.0f) * u8_max)); } + /** + * Green channel represented in an 8-bit unsigned value. + */ u8 to_g8() const { return static_cast(round32(clamp(this->g, 0.0f, 1.0f) * u8_max)); } + /** + * Blue channel represented in an 8-bit unsigned value. + */ u8 to_b8() const { return static_cast(round32(clamp(this->b, 0.0f, 1.0f) * u8_max)); } + /** + * Alpha channel represented in an 8-bit unsigned value. + */ u8 to_a8() const { return static_cast(round32(clamp(this->a, 0.0f, 1.0f) * u8_max)); } diff --git a/source/core/lalgebra.cpp b/source/core/lalgebra.cpp new file mode 100644 index 0000000..d7b988a --- /dev/null +++ b/source/core/lalgebra.cpp @@ -0,0 +1,40 @@ +export module core.lalgebra; + +import core; + +export namespace core { + /** + * Two-component vector type backed by 32-bit floating point values. + */ + struct vector2 { + /** + * "X" axis spatial component. + */ + f32 x; + + /** + * "Y" axis spatial component. + */ + f32 y; + }; + + /** + * Three-component vector type backed by 32-bit floating point values. + */ + struct vector3 { + /** + * "X" axis spatial component. + */ + f32 x; + + /** + * "Y" axis spatial component. + */ + f32 y; + + /** + * "Z" axis spatial component. + */ + f32 z; + }; +} diff --git a/source/core/math.cpp b/source/core/math.cpp deleted file mode 100644 index 1cc49b5..0000000 --- a/source/core/math.cpp +++ /dev/null @@ -1,19 +0,0 @@ -export module core.math; - -import core; - -export namespace core { - struct vector2 { - core::f32 x; - - core::f32 y; - }; - - struct vector3 { - core::f32 x; - - core::f32 y; - - core::f32 z; - }; -} diff --git a/source/core/sequence.cpp b/source/core/sequence.cpp index 79d0bf4..7e59719 100644 --- a/source/core/sequence.cpp +++ b/source/core/sequence.cpp @@ -3,116 +3,212 @@ export module core.sequence; import core; export namespace core { - enum class reserve_error { + /** + * Result codes used by [sequence]-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 sequence-like types. + * + * Sequences are any data structure which owns a linear, non-unique set of elements which may + * be queried and/or mutated. + */ template struct sequence { - sequence() = default; - - sequence(sequence const &) = delete; - virtual ~sequence() {}; - virtual slice as_slice() const = 0; - - virtual optional append(slice const & appended_elements) = 0; + /** + * Attempts to append `source_elements` to the sequence. + * + * The returned [append_result] indicates whether the operation was successful or not. + * + * If the returned [append_result] is anything but [append_result::ok], the [sequence] will + * be left in an implementation-defined state. + */ + virtual append_result append(slice const & source_elements) = 0; }; - template struct stack : public sequence { - stack(allocator * buffer_allocator) : filled{0}, - buffer{0}, buffer_allocator{buffer_allocator} { - - this->elements = this->buffer; + /** + * Last-in-first-out linear sequence 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. + */ + template struct stack : public sequence { + stack(allocator * dynamic_allocator) : local_buffer{0} { + this->dynamic_allocator = dynamic_allocator; + this->filled = 0; + this->elements = this->local_buffer; } ~stack() override { - if (this->elements.pointer != this->buffer) - this->buffer_allocator->deallocate(this->elements.pointer); + if (this->is_dynamic()) this->dynamic_allocator->deallocate(this->elements.pointer); } - slice as_slice() const override { - return this->elements; - } - - optional push(element const & pushed_element) { - if (this->filled == this->elements.length) { - optional const maybe_error = this->reserve(this->elements.length); - - if (maybe_error.has_value()) return maybe_error; - } - - this->elements[this->filled] = pushed_element; - this->filled += 1; - - return {}; - } - - optional append(slice const & pushed_elements) override { - usize const updated_fill = this->filled + pushed_elements.length; + /** + * 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 const & source_elements) override { + usize const updated_fill = this->filled + source_elements.length; if (updated_fill >= this->elements.length) { - optional const maybe_error = this->reserve(max(this->elements.length, updated_fill)); + append_result const result = + this->reserve(max(this->elements.length, updated_fill)); - if (maybe_error.has_value()) return maybe_error; + if (result != append_result::ok) return result; } - for (usize i = 0; i < pushed_elements.length; i += 1) - this->elements[this->filled + i] = pushed_elements[i]; + for (usize i = 0; i < source_elements.length; i += 1) + this->elements[this->filled + i] = source_elements[i]; this->filled = updated_fill; - return {}; + return append_result::ok; } - optional reserve(usize capacity) { + /** + * Returns the beginning of the elements as a mutable pointer. + */ + element * begin() { + return this->elements.pointer; + } + + /** + * Returns the beginning of the elements as a const pointer. + */ + element const * begin() const { + return this->elements.pointer; + } + + /** + * Returns the ending of the elements as a mutable pointer. + */ + element * end() { + return this->elements.pointer + this->filled; + } + + /** + * Returns the ending of the elements as a const pointer. + */ + element const * end() const { + return this->elements.pointer + this->filled; + } + + /** + * Returns `true` if the stack is backed by dynamic memory, otherwise `false`. + */ + bool is_dynamic() const { + return this->elements.pointer != 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->elements.length + capacity; - if (this->elements.pointer == this->buffer) { - u8 * const maybe_allocation = this->buffer_allocator-> - reallocate(nullptr, sizeof(element) * requested_capacity); - - if (maybe_allocation == nullptr) { - this->elements = {}; - - return reserve_error::out_of_memory; - } - - this->elements = {reinterpret_cast(maybe_allocation), requested_capacity}; - } else { - u8 * const maybe_allocation = this->buffer_allocator->reallocate( + if (this->is_dynamic()) { + // Grow dynamic buffer (bailing out if failed). + u8 * const buffer = this->dynamic_allocator->reallocate( reinterpret_cast(this->elements.pointer), sizeof(element) * requested_capacity); - if (maybe_allocation == nullptr) { + if (buffer == nullptr) { this->elements = {}; - return reserve_error::out_of_memory; + return append_result::out_of_memory; } - this->elements = {reinterpret_cast(maybe_allocation), requested_capacity}; + 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 append_result::out_of_memory; + } + + core::copy({buffer, buffer_size}, this->elements.as_bytes()); + + this->elements = {reinterpret_cast(buffer), requested_capacity}; } - return {}; + return append_result::ok; } private: - allocator * buffer_allocator; + allocator * dynamic_allocator; usize filled; slice elements; - element buffer[initial_capacity]; + element local_buffer[init_capacity]; }; + /** + * Writable type for appending data to a [sequence] containing [u8] values. + */ struct sequence_writer : public writable { - sequence_writer(sequence * target_sequence) : writable{[target_sequence](slice const & buffer) -> expected { - optional const maybe_error = target_sequence->append(buffer); - - if (maybe_error.has_value()) return io_error::unavailable; - - return buffer.length; - }} {} + sequence_writer(sequence * output_sequence) : writable{ + [output_sequence](slice const & buffer) -> expected { + switch (output_sequence->append(buffer)) { + case append_result::ok: return buffer.length; + case append_result::out_of_memory: return io_error::unavailable; + default: unreachable(); + } + }} {} }; } diff --git a/source/kym.cpp b/source/kym.cpp index 3ac4528..014aedc 100644 --- a/source/kym.cpp +++ b/source/kym.cpp @@ -1,7 +1,7 @@ export module kym; import core; -import core.math; +import core.lalgebra; export namespace kym { enum class value_type { diff --git a/source/kym/environment.cpp b/source/kym/environment.cpp index 87b26a6..ff0fa36 100644 --- a/source/kym/environment.cpp +++ b/source/kym/environment.cpp @@ -5,6 +5,8 @@ import core.sequence; import kym; +using loggable = core::callable const &)>; + export namespace kym { struct vm; } @@ -42,7 +44,7 @@ struct bytecode { } - bool compile(tokenizer bytecode_tokenizer) { + bool compile(tokenizer bytecode_tokenizer, loggable const & log_error) { for (;;) { token const initial_token = bytecode_tokenizer.next(); @@ -58,10 +60,6 @@ struct bytecode { return kym::nil; } - core::slice error_message() const { - return this->error_message_buffer.as_slice(); - } - private: core::stack error_message_buffer; }; @@ -71,8 +69,8 @@ export namespace kym { return nil; } - core::expected default_stringify(vm & owning_vm, void * userdata, core::writable const & writable) { - return writable(core::slice("[object]").as_bytes()); + core::expected default_stringify(vm & owning_vm, void * userdata, core::writable const & output) { + return output(core::slice("[object]").as_bytes()); } struct bound_object { @@ -100,8 +98,8 @@ export namespace kym { return this->behavior.call(*this->owning_vm, this->userdata, arguments); } - core::expected stringify(core::writable const & writable) { - return this->behavior.stringify(*this->owning_vm, this->userdata, writable); + core::expected stringify(core::writable const & output) { + return this->behavior.stringify(*this->owning_vm, this->userdata, output); } value get_field(core::slice const & field_name) { @@ -143,8 +141,9 @@ export namespace kym { if (source_bytecode == nullptr) return nil; - if (source_bytecode->compile(tokenizer{source})) { - this->log(source_bytecode->error_message()); + if (!source_bytecode->compile(tokenizer{source}, [&](core::slice error_message) { + this->log(error_message); + })) { this->allocator->deallocate(source_bytecode); return nil; @@ -176,7 +175,7 @@ export namespace kym { private: core::slice data_stack; - core::callable)> log; + loggable log; core::allocator * allocator; }; diff --git a/source/runtime.cpp b/source/runtime.cpp index 38de729..a933521 100644 --- a/source/runtime.cpp +++ b/source/runtime.cpp @@ -4,7 +4,7 @@ import app; import app.sdl; import core; -import core.math; +import core.lalgebra; import core.sequence; import kym; @@ -15,7 +15,7 @@ extern "C" int main(int argc, char const * const * argv) { constexpr app::path config_path = app::path::empty().joined("config.kym"); bool is_config_loaded = false; - system.bundle().read_file(config_path, [&](core::readable const & config_readable) { + system.bundle().read_file(config_path, [&](core::readable const & file) { kym::vm vm{&system.thread_safe_allocator(), [&system](core::slice const & error_message) { system.log(app::log_level::error, error_message); }}; @@ -29,14 +29,14 @@ extern "C" int main(int argc, char const * const * argv) { return; } - core::stack config_source{&system.thread_safe_allocator()}; - core::u8 config_source_stream_buffer[1024] = {}; + core::stack script_source{&system.thread_safe_allocator()}; + core::u8 stream_buffer[1024] = {}; - if (!core::stream(core::sequence_writer{&config_source}, - config_readable, config_source_stream_buffer).is_ok()) return; + if (!core::stream(core::sequence_writer{&script_source}, file, stream_buffer).is_ok()) + return; - vm.with_object(vm.compile(config_source.as_slice().as_chars()), [&](kym::bound_object & config_script) { - vm.with_object(config_script.call({}), [&](kym::bound_object & config) { + vm.with_object(vm.compile(core::slice{script_source.begin(), script_source.end()}.as_chars()), [&](kym::bound_object & script) { + vm.with_object(script.call({}), [&](kym::bound_object & config) { core::u16 const width = config.get_field("width").as_u16().value_or(0); if (width == 0) return system.log(app::log_level::error, From 340120d9209135ad8e323cb6f7ad59c17d147fb8 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 18 Feb 2023 20:23:03 +0000 Subject: [PATCH 06/35] Enable constexpr on core::equals --- source/core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core.cpp b/source/core.cpp index 4edacce..89de699 100644 --- a/source/core.cpp +++ b/source/core.cpp @@ -461,7 +461,7 @@ export namespace core { * Tests the equality of `a` against `b`, returning `true` if they contain identical bytes, * otherwise `false`. */ - bool equals(slice const & a, slice const & b) { + constexpr bool equals(slice const & a, slice const & b) { if (a.length != b.length) return false; for (size_t i = 0; i < a.length; i += 1) if (a[i] != b[i]) return false; From 44ee0591a2d0a7ecb609940a6a63cac50c6fb1a0 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 18 Feb 2023 20:48:06 +0000 Subject: [PATCH 07/35] Add more byte manipulation utils to core library --- source/core.cpp | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/source/core.cpp b/source/core.cpp index 89de699..7a6f66d 100644 --- a/source/core.cpp +++ b/source/core.cpp @@ -457,6 +457,32 @@ export namespace core { // Input/output operations. export namespace core { + /** + * Compares `a` and `b`, returning the difference between them or `0` if they are identical. + */ + constexpr size compare(slice const & a, slice const & b) { + usize const range = min(a.length, b.length); + + for (usize index = 0; index < range; index += 1) { + size const difference = static_cast(a[index]) - static_cast(b[index]); + + if (difference != 0) return difference; + } + + return static_cast(a.length) - static_cast(b.length); + } + + /** + * Copies the contents of `origin` into `target`. + * + * *Note*: safety-checked behavior is triggered if `target` is smaller than `origin`. + */ + void copy(slice const & target, slice const & origin) { + if (target.length < origin.length) core::unreachable(); + + for (usize i = 0; i < origin.length; i += 1) target[i] = origin[i]; + } + /** * Tests the equality of `a` against `b`, returning `true` if they contain identical bytes, * otherwise `false`. @@ -470,14 +496,14 @@ export namespace core { } /** - * Copies the contents of `origin` into `target`. - * - * *Note*: safety-checked behavior is triggered if `target` is smaller than `origin`. + * Returns a hash code generated from the values in `bytes`. */ - void copy(slice const & target, slice const & origin) { - if (target.length < origin.length) core::unreachable(); + constexpr usize hash(slice const & bytes) { + usize hash_code = 5381; - for (usize i = 0; i < origin.length; i += 1) target[i] = origin[i]; + for (u8 const byte : bytes) hash_code = ((hash_code << 5) + hash_code) + byte; + + return hash_code; } /** From a58b0d7bdb69b461e608a32c262bfb310935b7f9 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sat, 18 Feb 2023 23:49:31 +0000 Subject: [PATCH 08/35] Add disclaimer about path hash code --- source/core.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/core.cpp b/source/core.cpp index 7a6f66d..88b1140 100644 --- a/source/core.cpp +++ b/source/core.cpp @@ -497,6 +497,8 @@ export namespace core { /** * Returns a hash code generated from the values in `bytes`. + * + * *Note:* the returned hash code is not guaranteed to be unique. */ constexpr usize hash(slice const & bytes) { usize hash_code = 5381; From 51b73aa823cb8c4fefaa8200fdfa27ee9b72725a Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 02:02:51 +0000 Subject: [PATCH 09/35] Add file system interfacing to core library --- source/core/files.cpp | 137 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 source/core/files.cpp diff --git a/source/core/files.cpp b/source/core/files.cpp new file mode 100644 index 0000000..c014fec --- /dev/null +++ b/source/core/files.cpp @@ -0,0 +1,137 @@ +export module core.files; + +import core; + +export namespace core { + /** + * Platform-generalized identifier for a resource in a [file_store]. + */ + struct path { + /** + * Maximum path length. + */ + static usize const max = u8_max; + + /** + * Common path component separator. + */ + static char const seperator = '/'; + + constexpr path() : buffer{0} { + this->buffer[max] = max; + } + + /** + * Returns the base pointer of the path name. + */ + char const * begin() const { + return reinterpret_cast(this->buffer); + } + + /** + * Returns the number of bytes composing the path. + */ + constexpr usize byte_size() const { + return max - this->buffer[max]; + } + + /** + * Compares the path to `that`, returning the difference between the two paths or `0` if + * they are identical. + */ + constexpr size compare(path const & that) const { + return core::compare(slice{this->begin(), this->end()}.as_bytes(), + slice{that.begin(), that.end()}.as_bytes()); + } + + /** + * Returns the tail pointer of the path name. + */ + char const * end() const { + return reinterpret_cast(this->buffer) + this->byte_size(); + } + + /** + * Tests the path against `that` for equality, returning `true` if they are identical, + * otherwise `false`. + */ + constexpr bool equals(path const & that) const { + return core::equals(slice{this->begin(), this->end()}.as_bytes(), + slice{that.begin(), that.end()}.as_bytes()); + } + + /** + * Returns the path hash code. + * + * *Note:* the returned hash code is not guaranteed to be unique. + */ + constexpr u64 hash() const { + return core::hash(slice{this->begin(), this->end()}.as_bytes()); + } + + /** + * Returns a new [path] composed of the current path joined with `text`. + * + * *Note:* should the new path exceed [max] bytes in size, an empty [path] is returned instead. + */ + constexpr path joined(slice const & text) const { + if (text.length > this->buffer[max]) return path{}; + + path joined_path = *this; + + for (char const c : text) { + joined_path.buffer[joined_path.byte_size()] = c; + joined_path.buffer[max] -= 1; + } + + return joined_path; + } + + private: + u8 buffer[max + 1]; + }; + + /** + * Platform-generalized file system interface. + */ + struct fs { + /** + * The result from a file access operation. + * + * [file_result::ok] indicates a successful file access operation. + * + * [file_result::not_found] signals that the file was not found. + * + * [file_result::access_denied] warns that the file was found but cannot be opened through + * the file system interface. The most common scenario for this error is a lack of required + * file permissions. + */ + enum class [[nodiscard]] access_result { + ok, + not_found, + access_denied, + }; + + /** + * Attempts to read the file in the file system located at `file_path` relative, calling + * `then` if it was successfully opened for reading. + * + * The returned [access_result] indicates whether the operation was successful or not. + * + * Once `then` returns, access to the file is closed automatically. + */ + virtual access_result read_file(path const & file_path, + callable const & then) = 0; + + /** + * Attempts to write the file in the file system located at `file_path` relative, calling + * `then` if it was successfully opened for writing. + * + * The returned [access_result] indicates whether the operation was successful or not. + * + * Once `then` returns, access to the file is closed automatically. + */ + virtual access_result write_file(path const & file_path, + callable const & then) = 0; + }; +} From 22a8b63d0f711b62ca7ef85716cae9b5627f26b8 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 14:14:43 +0000 Subject: [PATCH 10/35] Add compile-time checked value constructor to path --- source/core/files.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/core/files.cpp b/source/core/files.cpp index c014fec..e71ab67 100644 --- a/source/core/files.cpp +++ b/source/core/files.cpp @@ -21,6 +21,12 @@ export namespace core { this->buffer[max] = max; } + template constexpr path(char const(&text)[text_size]) { + static_assert(text_size <= max, "path cannot be longer than maximum length"); + copy(this->buffer, text.as_bytes()); + zero(slice{this->buffer}.sliced(text_size, max - text_size)); + } + /** * Returns the base pointer of the path name. */ From 811176ad535410bc7c2d1002236af29e56d2ec0f Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 14:15:11 +0000 Subject: [PATCH 11/35] Expose way to zero memory in core library --- source/core.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/source/core.cpp b/source/core.cpp index 88b1140..3e6cc0f 100644 --- a/source/core.cpp +++ b/source/core.cpp @@ -480,7 +480,14 @@ export namespace core { void copy(slice const & target, slice const & origin) { if (target.length < origin.length) core::unreachable(); - for (usize i = 0; i < origin.length; i += 1) target[i] = origin[i]; + for (usize i = 0; i < target.length; i += 1) target[i] = origin[i]; + } + + /** + * Zeroes the contents of `target`. + */ + void zero(slice const & target) { + for (usize i = 0; i < target.length; i += 1) target[i] = 0; } /** From e64013f03b2b74fa07a9da896230b180c62e4099 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 14:24:17 +0000 Subject: [PATCH 12/35] Add missing note to core::path::begin --- source/core/files.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/core/files.cpp b/source/core/files.cpp index e71ab67..42cdd54 100644 --- a/source/core/files.cpp +++ b/source/core/files.cpp @@ -29,6 +29,8 @@ export namespace core { /** * Returns the base pointer of the path name. + * + * *Note*: the returned buffer pointer is guaranteed to end with a zero terminator. */ char const * begin() const { return reinterpret_cast(this->buffer); From f6ae40617e85d577c6ab4e8e63da2fd7dc4b5ae0 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 15:48:29 +0000 Subject: [PATCH 13/35] Fix compilation error in core::path constructor --- source/core/files.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/source/core/files.cpp b/source/core/files.cpp index 42cdd54..b159f02 100644 --- a/source/core/files.cpp +++ b/source/core/files.cpp @@ -21,10 +21,12 @@ export namespace core { this->buffer[max] = max; } - template constexpr path(char const(&text)[text_size]) { + template constexpr path(char const(&text)[text_size]) : path{} { static_assert(text_size <= max, "path cannot be longer than maximum length"); - copy(this->buffer, text.as_bytes()); - zero(slice{this->buffer}.sliced(text_size, max - text_size)); + + for (usize i = 0; i < text_size; i += 1) this->buffer[i] = text[i]; + + this->buffer[max] = max - text_size; } /** @@ -56,7 +58,7 @@ export namespace core { * Returns the tail pointer of the path name. */ char const * end() const { - return reinterpret_cast(this->buffer) + this->byte_size(); + return this->buffer + this->byte_size(); } /** @@ -96,7 +98,7 @@ export namespace core { } private: - u8 buffer[max + 1]; + char buffer[max + 1]; }; /** From 5394e8de679370db674aabcc94ddb38e7c6c5ec2 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 16:21:44 +0000 Subject: [PATCH 14/35] Add unsigned integer printing to core library --- source/core.cpp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/source/core.cpp b/source/core.cpp index 3e6cc0f..2b3ce38 100644 --- a/source/core.cpp +++ b/source/core.cpp @@ -515,6 +515,15 @@ export namespace core { return hash_code; } + /** + * Swaps the values of `element` in `a` and `b` around using copy semantics. + */ + template constexpr void swap(element & a, element & b) { + element const temp = a; + a = b; + b = temp; + } + /** * Streams the data from `input` to `output`, using `buffer` as temporary transfer space. * @@ -550,6 +559,34 @@ export namespace core { return written; } + /** + * 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(writable const & output, u64 value) { + if (value == 0) return output(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(slice{buffer, buffer_count}); + } + /** * Returns a reference to a shared [allocator] which will always return `nullptr` on calls to * [allocator::reallocate]. From 3a9a6a34bb0aa305a643ee7b3c30ac829d86f2ff Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 16:31:45 +0000 Subject: [PATCH 15/35] Fix runtime error regression in core::copy --- source/core/sequence.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/core/sequence.cpp b/source/core/sequence.cpp index 7e59719..316427b 100644 --- a/source/core/sequence.cpp +++ b/source/core/sequence.cpp @@ -70,8 +70,7 @@ export namespace core { usize const updated_fill = this->filled + source_elements.length; if (updated_fill >= this->elements.length) { - append_result const result = - this->reserve(max(this->elements.length, updated_fill)); + append_result const result = this->reserve(updated_fill); if (result != append_result::ok) return result; } @@ -155,7 +154,7 @@ export namespace core { * `initial_capacity` argument. */ append_result reserve(usize capacity) { - usize const requested_capacity = this->elements.length + capacity; + usize const requested_capacity = this->filled + capacity; if (this->is_dynamic()) { // Grow dynamic buffer (bailing out if failed). From 298c7bfe94d5ff304128a369c97cdeeec8e27ec3 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 16:32:02 +0000 Subject: [PATCH 16/35] Fix runtime error regression in core::copy --- source/core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core.cpp b/source/core.cpp index 2b3ce38..97b59aa 100644 --- a/source/core.cpp +++ b/source/core.cpp @@ -480,7 +480,7 @@ export namespace core { void copy(slice const & target, slice const & origin) { if (target.length < origin.length) core::unreachable(); - for (usize i = 0; i < target.length; i += 1) target[i] = origin[i]; + for (usize i = 0; i < origin.length; i += 1) target[i] = origin[i]; } /** From 125dcc4cdfa7c7e83d564be25300525ec483ba21 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 16:41:22 +0000 Subject: [PATCH 17/35] Remove static assert message in core::path constructor --- source/core/files.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/files.cpp b/source/core/files.cpp index b159f02..b244e19 100644 --- a/source/core/files.cpp +++ b/source/core/files.cpp @@ -22,7 +22,7 @@ export namespace core { } template constexpr path(char const(&text)[text_size]) : path{} { - static_assert(text_size <= max, "path cannot be longer than maximum length"); + static_assert(text_size <= max); for (usize i = 0; i < text_size; i += 1) this->buffer[i] = text[i]; From 9af0e860cb77a0434881d43ec355437037c68358 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 16:43:30 +0000 Subject: [PATCH 18/35] Add skeleton of oar library --- source/oar.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 source/oar.cpp diff --git a/source/oar.cpp b/source/oar.cpp new file mode 100644 index 0000000..dacae13 --- /dev/null +++ b/source/oar.cpp @@ -0,0 +1,44 @@ +export module oar; + +import core; +import core.files; + +export namespace oar { + constexpr core::usize signature_length{4}; + + constexpr core::u8 signature_magic[signature_length]{'o', 'a', 'r', 0}; + + struct entry { + core::u8 signature_magic[signature_length]; + + core::path path; + + core::u64 data_offset; + + core::u64 data_length; + + core::u8 padding[244]; + }; + + struct archive : public core::fs { + using core::fs::access_result; + + archive() { + + } + + access_result read_file(core::path const & file_path, + core::callable const & then) override { + + return access_result::access_denied; + } + + virtual access_result write_file(core::path const & file_path, + core::callable const & then) override { + + return access_result::access_denied; + } + }; +} + +static_assert(sizeof(oar::entry) == 512); From a4e68546237715bb7edfd62190a2926a3ee0bd97 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 16:43:47 +0000 Subject: [PATCH 19/35] Add oar library to build script --- build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/build.py b/build.py index 93acd4e..465a3db 100755 --- a/build.py +++ b/build.py @@ -29,6 +29,7 @@ def compile_package(root_module_name: str) -> None: compile_module(os.path.join(root_module_source_path, file_name), f"{root_module_name}.{os.path.splitext(file_name)[0]}") compile_package("core") +compile_package("oar") compile_package("app") compile_package("kym") compile_package("runtime") From be0527dac8d02c2de3192327d681e79b4e3ba47e Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 16:44:16 +0000 Subject: [PATCH 20/35] Fix build error in kym::default_stringify --- source/kym/environment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/kym/environment.cpp b/source/kym/environment.cpp index ff0fa36..038e117 100644 --- a/source/kym/environment.cpp +++ b/source/kym/environment.cpp @@ -70,7 +70,7 @@ export namespace kym { } core::expected default_stringify(vm & owning_vm, void * userdata, core::writable const & output) { - return output(core::slice("[object]").as_bytes()); + return output(core::slice{"[object]"}.as_bytes()); } struct bound_object { From 88fabfaa5109dc24c2dcac06bb027cac7f3d15d7 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 16:45:40 +0000 Subject: [PATCH 21/35] Clean up app library --- source/app.cpp | 281 +++++++++++++++++++++++++++++++++++++-------- source/app/sdl.cpp | 167 --------------------------- source/runtime.cpp | 32 +++--- 3 files changed, 248 insertions(+), 232 deletions(-) delete mode 100644 source/app/sdl.cpp diff --git a/source/app.cpp b/source/app.cpp index 84a3f56..edc539d 100644 --- a/source/app.cpp +++ b/source/app.cpp @@ -1,61 +1,103 @@ +module; + +#include + export module app; import core; +import core.files; import core.image; import core.lalgebra; +import oar; + export namespace app { - struct path { - static constexpr core::usize max = 0xff; + struct directory : public core::fs { + using core::fs::access_result; - static constexpr char seperator = '/'; + struct rules { + bool can_read; - core::u8 buffer[max + 1]; + bool can_write; + }; - static constexpr path empty() { - path empty_path = {0}; + directory() : path_buffer{0} {} - empty_path.buffer[max] = max; + access_result read_file(core::path const & file_path, + core::callable const & then) { - return empty_path; + if (this->prefix_length == 0) return access_result::not_found; + + if (!this->access_rules.can_read) return access_result::access_denied; + + ::SDL_RWops * rw_ops = this->open_rw(file_path, {.can_read = true}); + + if (rw_ops == nullptr) return access_result::not_found; + + then([rw_ops](core::slice const & buffer) -> size_t { + return ::SDL_RWread(rw_ops, buffer.pointer, sizeof(uint8_t), buffer.length); + }); + + ::SDL_RWclose(rw_ops); + + return access_result::ok; } - core::slice as_slice() const { - return {reinterpret_cast(this->buffer), this->size()}; - } + void target(core::slice const & directory_path, rules const & access_rules) { + this->access_rules = access_rules; + this->prefix_length = core::min(directory_path.length, path_max - 1); - constexpr core::usize size() const { - return max - this->buffer[max]; - } + { + core::slice const path_buffer_slice{this->path_buffer}; - core::i16 compare(path const & that) { - return 0; - } + core::copy(path_buffer_slice.sliced(0, this->prefix_length), + directory_path.sliced(0, this->prefix_length).as_bytes()); - bool equals(path const & that) const { - return core::equals(this->as_slice().as_bytes(), that.as_slice().as_bytes()); - } - - constexpr path joined(core::slice const & text) const { - if (text.length > this->buffer[max]) return empty(); - - path joined_path = *this; - - for (char const c : text) { - joined_path.buffer[joined_path.size()] = c; - joined_path.buffer[max] -= 1; + core::zero(path_buffer_slice.sliced(this->prefix_length, path_max - this->prefix_length)); } - - return joined_path; } - core::u64 hash() { - return 0; - } - }; + access_result write_file(core::path const & file_path, + core::callable const & then) { - struct file_store { - virtual void read_file(app::path const & file_path, core::callable const & then) = 0; + if (this->prefix_length == 0) return access_result::not_found; + + if (!this->access_rules.can_write) return access_result::access_denied; + + ::SDL_RWops * rw_ops = this->open_rw(file_path, {.can_write = true}); + + if (rw_ops == nullptr) return access_result::not_found; + + then([rw_ops](core::slice const & buffer) -> size_t { + return ::SDL_RWwrite(rw_ops, buffer.pointer, sizeof(uint8_t), buffer.length); + }); + + ::SDL_RWclose(rw_ops); + + return access_result::ok; + } + + private: + static constexpr core::usize path_max = 4096; + + rules access_rules; + + core::usize prefix_length; + + core::u8 path_buffer[path_max]; + + ::SDL_RWops * open_rw(core::path const & file_path, rules const & file_rules) { + core::u8 * const path_begin = this->path_buffer + this->prefix_length; + + core::slice const path_remaining = + {path_begin, path_begin + (path_max - this->prefix_length) - 1}; + + if (path_remaining.length < file_path.byte_size()) return nullptr; + + core::copy(path_remaining, core::slice{file_path.begin(), file_path.end()}.as_bytes()); + + return ::SDL_RWFromFile(reinterpret_cast(this->path_buffer), "r"); + } }; enum class log_level { @@ -65,18 +107,103 @@ export namespace app { }; struct system { - virtual bool poll() = 0; + system(core::path const & title) : res_archive{} { + constexpr directory::rules read_only_rules = {.can_read = true}; - virtual file_store & bundle() = 0; + { + char * const path = ::SDL_GetBasePath(); - virtual void log(log_level level, core::slice const & message) = 0; + if (path == nullptr) { + this->cwd_directory.target("./", read_only_rules); + } else { + core::usize path_length = 0; - virtual core::allocator & thread_safe_allocator() = 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) { + core::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); + } + } + + core::fs & cwd_fs() { + return this->cwd_directory; + } + + bool poll() { + while (::SDL_PollEvent(&this->sdl_event) != 0) { + switch (this->sdl_event.type) { + case SDL_QUIT: return false; + } + } + + return true; + } + + core::fs & res_fs() { + return this->res_archive; + } + + void log(app::log_level level, core::slice const & message) { + core::i32 const length = static_cast( + core::min(message.length, static_cast(core::i32_max))); + + ::SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, + SDL_LOG_PRIORITY_INFO, "%.*s", length, message.pointer); + } + + core::allocator & thread_safe_allocator() { + return this->allocator; + } + + core::fs & user_fs() { + return this->user_directory; + } + + private: + ::SDL_Event sdl_event; + + struct : public core::allocator { + core::u8 * reallocate(core::u8 * maybe_allocation, core::usize requested_size) override { + return reinterpret_cast(::SDL_malloc(requested_size)); + } + + void deallocate(void * allocation) override { + ::SDL_free(allocation); + } + } allocator; + + oar::archive res_archive; + + directory cwd_directory; + + directory user_directory; }; struct graphics { - enum class show_error { - none, + enum class [[nodiscard]] show_result { + ok, out_of_memory, }; @@ -84,12 +211,72 @@ export namespace app { core::color background_color; }; - virtual void render(canvas & source_canvas) = 0; + graphics(core::path const & title) { + this->retitle(title); + } - virtual void present() = 0; + void render(canvas & source_canvas) { + if (this->sdl_renderer != nullptr) { + SDL_SetRenderDrawColor(this->sdl_renderer, source_canvas.background_color.to_r8(), + source_canvas.background_color.to_g8(), source_canvas.background_color.to_b8(), + source_canvas.background_color.to_a8()); - virtual show_error show(core::u16 physical_width, core::u16 physical_height) = 0; + SDL_RenderClear(this->sdl_renderer); + } + } - virtual void retitle(core::slice const & updated_title) = 0; + void retitle(core::path const & title) { + this->title = title; + + if (this->sdl_window != nullptr) + ::SDL_SetWindowTitle(this->sdl_window, this->title.begin()); + } + + show_result show(core::u16 physical_width, core::u16 physical_height) { + if (this->sdl_window == nullptr) { + constexpr int sdl_windowpos = SDL_WINDOWPOS_UNDEFINED; + constexpr core::u32 sdl_windowflags = 0; + + this->sdl_window = ::SDL_CreateWindow(this->title.begin(), sdl_windowpos, + sdl_windowpos, static_cast(physical_width), static_cast(physical_height), + sdl_windowflags); + + if (this->sdl_window == nullptr) return show_result::out_of_memory; + } else { + ::SDL_ShowWindow(this->sdl_window); + } + + if (this->sdl_renderer == nullptr) { + constexpr core::u32 sdl_rendererflags = 0; + + this->sdl_renderer = ::SDL_CreateRenderer(this->sdl_window, -1, sdl_rendererflags); + + if (this->sdl_renderer == nullptr) return show_result::out_of_memory; + } + + return show_result::ok; + } + + void present() { + if (this->sdl_renderer != nullptr) { + ::SDL_RenderPresent(this->sdl_renderer); + } + } + + private: + core::path title; + + ::SDL_Window * sdl_window = nullptr; + + ::SDL_Renderer * sdl_renderer = nullptr; }; + + using graphical_runnable = core::callable; + + int display(core::path const & title, graphical_runnable const & run) { + system app_system{title}; + graphics app_graphics{title}; + + return run(app_system, app_graphics); + } } diff --git a/source/app/sdl.cpp b/source/app/sdl.cpp deleted file mode 100644 index c332c6b..0000000 --- a/source/app/sdl.cpp +++ /dev/null @@ -1,167 +0,0 @@ -module; - -#include - -export module app.sdl; - -import app; - -import core; -import core.image; -import core.sequence; -import core.lalgebra; - -struct bundled_file_store : public app::file_store { - void read_file(app::path const & file_path, core::callable const & then) override { - constexpr core::slice path_prefix = "./"; - constexpr core::usize path_max = 512; - - if ((file_path.size() + path_prefix.length) > path_max) core::unreachable(); - - core::stack path_buffer{&core::null_allocator()}; - - if (path_buffer.append("./") != core::append_result::ok) core::unreachable(); - - // File path is guaranteed to be null-terminated. - if (path_buffer.append(file_path.as_slice()) != core::append_result::ok) - core::unreachable(); - - SDL_RWops * rw_ops = ::SDL_RWFromFile(path_buffer.begin(), "r"); - - if (rw_ops == nullptr) return; - - then([rw_ops](core::slice const & buffer) -> size_t { - return ::SDL_RWread(rw_ops, buffer.pointer, sizeof(uint8_t), buffer.length); - }); - - ::SDL_RWclose(rw_ops); - } -}; - -struct sdl_allocator : public core::allocator { - core::u8 * reallocate(core::u8 * maybe_allocation, core::usize requested_size) override { - return reinterpret_cast(::SDL_malloc(requested_size)); - } - - void deallocate(void * allocation) override { - ::SDL_free(allocation); - } -}; - -struct sdl_system : public app::system { - private: - ::SDL_Event sdl_event; - - sdl_allocator allocator; - - bundled_file_store bundled_store; - - public: - sdl_system() : - sdl_event{0}, - allocator{} {} - - bool poll() override { - while (::SDL_PollEvent(&this->sdl_event) != 0) { - switch (this->sdl_event.type) { - case SDL_QUIT: return false; - } - } - - return true; - } - - app::file_store & bundle() override { - return this->bundled_store; - } - - void log(app::log_level level, core::slice const & message) override { - core::i32 const length = static_cast( - core::min(message.length, static_cast(core::i32_max))); - - ::SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, - SDL_LOG_PRIORITY_INFO, "%.*s", length, message.pointer); - } - - core::allocator & thread_safe_allocator() override { - return this->allocator; - } -}; - -struct sdl_graphics : public app::graphics { - static constexpr core::usize title_maximum = 128; - - core::u32 title_length; - - char title_buffer[title_maximum]; - - ::SDL_Window * sdl_window = nullptr; - - ::SDL_Renderer * sdl_renderer = nullptr; - - sdl_graphics(core::slice const & title) { - this->retitle(title); - } - - void render(canvas & source_canvas) override { - if (this->sdl_renderer != nullptr) { - SDL_SetRenderDrawColor(this->sdl_renderer, source_canvas.background_color.to_r8(), - source_canvas.background_color.to_g8(), source_canvas.background_color.to_b8(), - source_canvas.background_color.to_a8()); - - SDL_RenderClear(this->sdl_renderer); - } - } - - void retitle(core::slice const & title) override { - this->title_length = core::min(title.length, title_maximum - 1); - - for (core::usize i = 0; i < this->title_length; i += 1) title_buffer[i] = title[i]; - - for (core::usize i = this->title_length; i < title_maximum; i += 1) title_buffer[i] = 0; - - if (this->sdl_window != nullptr) ::SDL_SetWindowTitle(this->sdl_window, title_buffer); - } - - app::graphics::show_error show(core::u16 physical_width, core::u16 physical_height) override { - if (this->sdl_window == nullptr) { - constexpr int sdl_windowpos = SDL_WINDOWPOS_UNDEFINED; - constexpr core::u32 sdl_windowflags = 0; - - this->sdl_window = ::SDL_CreateWindow(title_buffer, sdl_windowpos, sdl_windowpos, - static_cast(physical_width), static_cast(physical_height), - sdl_windowflags); - - if (this->sdl_window == nullptr) return show_error::out_of_memory; - } else { - ::SDL_ShowWindow(this->sdl_window); - } - - if (this->sdl_renderer == nullptr) { - constexpr core::u32 sdl_rendererflags = 0; - - this->sdl_renderer = ::SDL_CreateRenderer(this->sdl_window, -1, sdl_rendererflags); - - if (this->sdl_renderer == nullptr) return show_error::out_of_memory; - } - - return show_error::none; - } - - void present() override { - if (this->sdl_renderer != nullptr) { - ::SDL_RenderPresent(this->sdl_renderer); - } - } -}; - -export namespace app { - int display(core::slice const & title, - core::callable const & run) { - - sdl_system system; - sdl_graphics graphics(title); - - return run(system, graphics); - } -} diff --git a/source/runtime.cpp b/source/runtime.cpp index a933521..bed15e1 100644 --- a/source/runtime.cpp +++ b/source/runtime.cpp @@ -1,9 +1,9 @@ export module runtime; import app; -import app.sdl; import core; +import core.files; import core.lalgebra; import core.sequence; @@ -12,13 +12,14 @@ import kym.environment; extern "C" int main(int argc, char const * const * argv) { return app::display("Ona Runtime", [](app::system & system, app::graphics & graphics) -> int { - constexpr app::path config_path = app::path::empty().joined("config.kym"); - bool is_config_loaded = false; + constexpr core::path config_path{"config.kym"}; + bool is_config_loaded{false}; - system.bundle().read_file(config_path, [&](core::readable const & file) { - kym::vm vm{&system.thread_safe_allocator(), [&system](core::slice const & error_message) { - system.log(app::log_level::error, error_message); - }}; + if ((system.res_fs().read_file(config_path, [&](core::readable const & file) { + kym::vm vm{&system.thread_safe_allocator(), + [&system](core::slice const & error_message) { + system.log(app::log_level::error, error_message); + }}; if (!vm.init({ .datastack_size = 64, @@ -30,24 +31,25 @@ extern "C" int main(int argc, char const * const * argv) { } core::stack script_source{&system.thread_safe_allocator()}; - core::u8 stream_buffer[1024] = {}; + core::u8 stream_buffer[1024]{0}; if (!core::stream(core::sequence_writer{&script_source}, file, stream_buffer).is_ok()) return; vm.with_object(vm.compile(core::slice{script_source.begin(), script_source.end()}.as_chars()), [&](kym::bound_object & script) { vm.with_object(script.call({}), [&](kym::bound_object & config) { - core::u16 const width = config.get_field("width").as_u16().value_or(0); + core::u16 const width{config.get_field("width").as_u16().value_or(0)}; if (width == 0) return system.log(app::log_level::error, "failed to decode `width` property of config"); - core::u16 const height = config.get_field("height").as_u16().value_or(0); + core::u16 const height{config.get_field("height").as_u16().value_or(0)}; if (height == 0) return system.log(app::log_level::error, "failed to decode `height` property of config"); - graphics.show(width, height); + if (graphics.show(width, height) != app::graphics::show_result::ok) + return system.log(app::log_level::error, "failed to initialize window"); vm.with_object(config.get_field("title"), [&](kym::bound_object & title) { core::stack title_buffer{&system.thread_safe_allocator()}; @@ -63,13 +65,7 @@ extern "C" int main(int argc, char const * const * argv) { }); }); }); - }); - - if (!is_config_loaded) { - system.log(app::log_level::error, "failed to load config"); - - return core::u8_max; - } + }) != core::fs::access_result::ok) || (!is_config_loaded)) return core::u8_max; // app::canvas canvas_2d(); From e711b4d50621899dd1ed0c49172fd7f40b57fe2a Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 16:47:16 +0000 Subject: [PATCH 22/35] Rename core.lalgebra to core.math --- source/app.cpp | 2 +- source/core/{lalgebra.cpp => math.cpp} | 2 +- source/kym.cpp | 2 +- source/runtime.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename source/core/{lalgebra.cpp => math.cpp} (94%) diff --git a/source/app.cpp b/source/app.cpp index edc539d..8aaf2de 100644 --- a/source/app.cpp +++ b/source/app.cpp @@ -7,7 +7,7 @@ export module app; import core; import core.files; import core.image; -import core.lalgebra; +import core.math; import oar; diff --git a/source/core/lalgebra.cpp b/source/core/math.cpp similarity index 94% rename from source/core/lalgebra.cpp rename to source/core/math.cpp index d7b988a..157b43f 100644 --- a/source/core/lalgebra.cpp +++ b/source/core/math.cpp @@ -1,4 +1,4 @@ -export module core.lalgebra; +export module core.math; import core; diff --git a/source/kym.cpp b/source/kym.cpp index 014aedc..3ac4528 100644 --- a/source/kym.cpp +++ b/source/kym.cpp @@ -1,7 +1,7 @@ export module kym; import core; -import core.lalgebra; +import core.math; export namespace kym { enum class value_type { diff --git a/source/runtime.cpp b/source/runtime.cpp index bed15e1..24af2e5 100644 --- a/source/runtime.cpp +++ b/source/runtime.cpp @@ -4,7 +4,7 @@ import app; import core; import core.files; -import core.lalgebra; +import core.math; import core.sequence; import kym; From d8ab57844d4071a4b7bb4eda7fe452087c89e31c Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 16:50:29 +0000 Subject: [PATCH 23/35] Rename core library to "coral" --- build.py | 2 +- source/app.cpp | 92 ++++++++++++++--------------- source/{core.cpp => coral.cpp} | 32 +++++----- source/{core => coral}/files.cpp | 12 ++-- source/{core => coral}/image.cpp | 6 +- source/{core => coral}/math.cpp | 6 +- source/{core => coral}/sequence.cpp | 8 +-- source/kym.cpp | 18 +++--- source/kym/environment.cpp | 62 +++++++++---------- source/oar.cpp | 30 +++++----- source/runtime.cpp | 32 +++++----- 11 files changed, 150 insertions(+), 150 deletions(-) rename source/{core.cpp => coral.cpp} (94%) rename source/{core => coral}/files.cpp (93%) rename source/{core => coral}/image.cpp (93%) rename source/{core => coral}/math.cpp (88%) rename source/{core => coral}/sequence.cpp (97%) diff --git a/build.py b/build.py index 465a3db..e05da45 100755 --- a/build.py +++ b/build.py @@ -28,7 +28,7 @@ def compile_package(root_module_name: str) -> None: for file_name in os.listdir(root_module_source_path): compile_module(os.path.join(root_module_source_path, file_name), f"{root_module_name}.{os.path.splitext(file_name)[0]}") -compile_package("core") +compile_package("coral") compile_package("oar") compile_package("app") compile_package("kym") diff --git a/source/app.cpp b/source/app.cpp index 8aaf2de..64bd585 100644 --- a/source/app.cpp +++ b/source/app.cpp @@ -4,16 +4,16 @@ module; export module app; -import core; -import core.files; -import core.image; -import core.math; +import coral; +import coral.files; +import coral.image; +import coral.math; import oar; export namespace app { - struct directory : public core::fs { - using core::fs::access_result; + struct directory : public coral::fs { + using coral::fs::access_result; struct rules { bool can_read; @@ -23,8 +23,8 @@ export namespace app { directory() : path_buffer{0} {} - access_result read_file(core::path const & file_path, - core::callable const & then) { + access_result read_file(coral::path const & file_path, + coral::callable const & then) { if (this->prefix_length == 0) return access_result::not_found; @@ -34,7 +34,7 @@ export namespace app { if (rw_ops == nullptr) return access_result::not_found; - then([rw_ops](core::slice const & buffer) -> size_t { + then([rw_ops](coral::slice const & buffer) -> size_t { return ::SDL_RWread(rw_ops, buffer.pointer, sizeof(uint8_t), buffer.length); }); @@ -43,22 +43,22 @@ export namespace app { return access_result::ok; } - void target(core::slice const & directory_path, rules const & access_rules) { + void target(coral::slice const & directory_path, rules const & access_rules) { this->access_rules = access_rules; - this->prefix_length = core::min(directory_path.length, path_max - 1); + this->prefix_length = coral::min(directory_path.length, path_max - 1); { - core::slice const path_buffer_slice{this->path_buffer}; + coral::slice const path_buffer_slice{this->path_buffer}; - core::copy(path_buffer_slice.sliced(0, this->prefix_length), + coral::copy(path_buffer_slice.sliced(0, this->prefix_length), directory_path.sliced(0, this->prefix_length).as_bytes()); - core::zero(path_buffer_slice.sliced(this->prefix_length, path_max - this->prefix_length)); + coral::zero(path_buffer_slice.sliced(this->prefix_length, path_max - this->prefix_length)); } } - access_result write_file(core::path const & file_path, - core::callable const & then) { + access_result write_file(coral::path const & file_path, + coral::callable const & then) { if (this->prefix_length == 0) return access_result::not_found; @@ -68,7 +68,7 @@ export namespace app { if (rw_ops == nullptr) return access_result::not_found; - then([rw_ops](core::slice const & buffer) -> size_t { + then([rw_ops](coral::slice const & buffer) -> size_t { return ::SDL_RWwrite(rw_ops, buffer.pointer, sizeof(uint8_t), buffer.length); }); @@ -78,23 +78,23 @@ export namespace app { } private: - static constexpr core::usize path_max = 4096; + static constexpr coral::usize path_max = 4096; rules access_rules; - core::usize prefix_length; + coral::usize prefix_length; - core::u8 path_buffer[path_max]; + coral::u8 path_buffer[path_max]; - ::SDL_RWops * open_rw(core::path const & file_path, rules const & file_rules) { - core::u8 * const path_begin = this->path_buffer + this->prefix_length; + ::SDL_RWops * open_rw(coral::path const & file_path, rules const & file_rules) { + coral::u8 * const path_begin = this->path_buffer + this->prefix_length; - core::slice const path_remaining = + 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; - core::copy(path_remaining, core::slice{file_path.begin(), file_path.end()}.as_bytes()); + coral::copy(path_remaining, coral::slice{file_path.begin(), file_path.end()}.as_bytes()); return ::SDL_RWFromFile(reinterpret_cast(this->path_buffer), "r"); } @@ -107,7 +107,7 @@ export namespace app { }; struct system { - system(core::path const & title) : res_archive{} { + system(coral::path const & title) : res_archive{} { constexpr directory::rules read_only_rules = {.can_read = true}; { @@ -116,7 +116,7 @@ export namespace app { if (path == nullptr) { this->cwd_directory.target("./", read_only_rules); } else { - core::usize path_length = 0; + coral::usize path_length = 0; while (path[path_length] != 0) path_length += 1; @@ -134,7 +134,7 @@ export namespace app { char * const path = ::SDL_GetPrefPath("ona", title.begin()); if (path != nullptr) { - core::usize path_length = 0; + coral::usize path_length = 0; while (path[path_length] != 0) path_length += 1; @@ -147,7 +147,7 @@ export namespace app { } } - core::fs & cwd_fs() { + coral::fs & cwd_fs() { return this->cwd_directory; } @@ -161,32 +161,32 @@ export namespace app { return true; } - core::fs & res_fs() { + coral::fs & res_fs() { return this->res_archive; } - void log(app::log_level level, core::slice const & message) { - core::i32 const length = static_cast( - core::min(message.length, static_cast(core::i32_max))); + void log(app::log_level level, coral::slice const & message) { + coral::i32 const length = static_cast( + coral::min(message.length, static_cast(coral::i32_max))); ::SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "%.*s", length, message.pointer); } - core::allocator & thread_safe_allocator() { + coral::allocator & thread_safe_allocator() { return this->allocator; } - core::fs & user_fs() { + coral::fs & user_fs() { return this->user_directory; } private: ::SDL_Event sdl_event; - struct : public core::allocator { - core::u8 * reallocate(core::u8 * maybe_allocation, core::usize requested_size) override { - return reinterpret_cast(::SDL_malloc(requested_size)); + struct : public coral::allocator { + coral::u8 * reallocate(coral::u8 * maybe_allocation, coral::usize requested_size) override { + return reinterpret_cast(::SDL_malloc(requested_size)); } void deallocate(void * allocation) override { @@ -208,10 +208,10 @@ export namespace app { }; struct canvas { - core::color background_color; + coral::color background_color; }; - graphics(core::path const & title) { + graphics(coral::path const & title) { this->retitle(title); } @@ -225,17 +225,17 @@ export namespace app { } } - void retitle(core::path const & title) { + void retitle(coral::path const & title) { this->title = title; if (this->sdl_window != nullptr) ::SDL_SetWindowTitle(this->sdl_window, this->title.begin()); } - show_result show(core::u16 physical_width, core::u16 physical_height) { + show_result show(coral::u16 physical_width, coral::u16 physical_height) { if (this->sdl_window == nullptr) { constexpr int sdl_windowpos = SDL_WINDOWPOS_UNDEFINED; - constexpr core::u32 sdl_windowflags = 0; + constexpr coral::u32 sdl_windowflags = 0; this->sdl_window = ::SDL_CreateWindow(this->title.begin(), sdl_windowpos, sdl_windowpos, static_cast(physical_width), static_cast(physical_height), @@ -247,7 +247,7 @@ export namespace app { } if (this->sdl_renderer == nullptr) { - constexpr core::u32 sdl_rendererflags = 0; + constexpr coral::u32 sdl_rendererflags = 0; this->sdl_renderer = ::SDL_CreateRenderer(this->sdl_window, -1, sdl_rendererflags); @@ -264,16 +264,16 @@ export namespace app { } private: - core::path title; + coral::path title; ::SDL_Window * sdl_window = nullptr; ::SDL_Renderer * sdl_renderer = nullptr; }; - using graphical_runnable = core::callable; + using graphical_runnable = coral::callable; - int display(core::path const & title, graphical_runnable const & run) { + int display(coral::path const & title, graphical_runnable const & run) { system app_system{title}; graphics app_graphics{title}; diff --git a/source/core.cpp b/source/coral.cpp similarity index 94% rename from source/core.cpp rename to source/coral.cpp index 97b59aa..7d7b703 100644 --- a/source/core.cpp +++ b/source/coral.cpp @@ -3,10 +3,10 @@ module; #include #include -export module core; +export module coral; // Runtime utilities. -export namespace core { +export namespace coral { /** * Triggers safety-checked behavior in debug mode. * @@ -19,7 +19,7 @@ export namespace core { } // Concrete and interface types. -export namespace core { +export namespace coral { using usize = size_t; using size = __ssize_t; @@ -180,7 +180,7 @@ export namespace core { } constexpr type & operator[](usize index) const { - if (this->length <= index) core::unreachable(); + if (this->length <= index) unreachable(); return this->pointer[index]; } @@ -188,7 +188,7 @@ export namespace core { } // Math functions. -export namespace core { +export namespace coral { /** * Returns the maximum value between `a` and `b`. */ @@ -225,8 +225,8 @@ export namespace core { * *Note*: passing an `buffer` smaller than `requested_size` will result in safety-checked * behavior. */ -export void * operator new(core::usize requested_size, core::slice const & buffer) { - if (buffer.length < requested_size) core::unreachable(); +export void * operator new(coral::usize requested_size, coral::slice const & buffer) { + if (buffer.length < requested_size) coral::unreachable(); return buffer.pointer; } @@ -239,8 +239,8 @@ export void * operator new(core::usize requested_size, core::slice con * *Note*: passing an `buffer` smaller than `requested_size` will result in safety-checked * behavior. */ -export void * operator new[](core::usize requested_size, core::slice const & buffer) { - if (buffer.length < requested_size) core::unreachable(); +export void * operator new[](coral::usize requested_size, coral::slice const & buffer) { + if (buffer.length < requested_size) coral::unreachable(); return buffer.pointer; } @@ -249,10 +249,10 @@ export void * operator new[](core::usize requested_size, core::slice c * Attempts to allocate and initialize a type of `requested_size` using `allocator`. * * *Note*: If the returned address is a non-`nullptr`, it should be deallocated prior to program - * exit. This may be achieved through either [core::allocator::deallocate] or implementation- + * exit. This may be achieved through either [coral::allocator::deallocate] or implementation- * specific allocator functionality. */ -export [[nodiscard]] void * operator new(core::usize requested_size, core::allocator & allocator) { +export [[nodiscard]] void * operator new(coral::usize requested_size, coral::allocator & allocator) { return allocator.reallocate(nullptr, requested_size); } @@ -260,15 +260,15 @@ export [[nodiscard]] void * operator new(core::usize requested_size, core::alloc * Attempts to allocate and initialize a series of types of `requested_size` using `allocator`. * * *Note*: If the returned address is a non-`nullptr`, it should be deallocated prior to program - * exit. This may be achieved through either [core::allocator::deallocate] or implementation- + * exit. This may be achieved through either [coral::allocator::deallocate] or implementation- * specific allocator functionality. */ -export [[nodiscard]] void * operator new[](core::usize requested_size, core::allocator & allocator) { +export [[nodiscard]] void * operator new[](coral::usize requested_size, coral::allocator & allocator) { return allocator.reallocate(nullptr, requested_size); } // Wrapper types. -export namespace core { +export namespace coral { /** * Monadic container for a single-`element` value or nothing. */ @@ -456,7 +456,7 @@ export namespace core { } // Input/output operations. -export namespace core { +export namespace coral { /** * Compares `a` and `b`, returning the difference between them or `0` if they are identical. */ @@ -478,7 +478,7 @@ export namespace core { * *Note*: safety-checked behavior is triggered if `target` is smaller than `origin`. */ void copy(slice const & target, slice const & origin) { - if (target.length < origin.length) core::unreachable(); + if (target.length < origin.length) coral::unreachable(); for (usize i = 0; i < origin.length; i += 1) target[i] = origin[i]; } diff --git a/source/core/files.cpp b/source/coral/files.cpp similarity index 93% rename from source/core/files.cpp rename to source/coral/files.cpp index b244e19..67330d1 100644 --- a/source/core/files.cpp +++ b/source/coral/files.cpp @@ -1,8 +1,8 @@ -export module core.files; +export module coral.files; -import core; +import coral; -export namespace core { +export namespace coral { /** * Platform-generalized identifier for a resource in a [file_store]. */ @@ -50,7 +50,7 @@ export namespace core { * they are identical. */ constexpr size compare(path const & that) const { - return core::compare(slice{this->begin(), this->end()}.as_bytes(), + return coral::compare(slice{this->begin(), this->end()}.as_bytes(), slice{that.begin(), that.end()}.as_bytes()); } @@ -66,7 +66,7 @@ export namespace core { * otherwise `false`. */ constexpr bool equals(path const & that) const { - return core::equals(slice{this->begin(), this->end()}.as_bytes(), + return coral::equals(slice{this->begin(), this->end()}.as_bytes(), slice{that.begin(), that.end()}.as_bytes()); } @@ -76,7 +76,7 @@ export namespace core { * *Note:* the returned hash code is not guaranteed to be unique. */ constexpr u64 hash() const { - return core::hash(slice{this->begin(), this->end()}.as_bytes()); + return coral::hash(slice{this->begin(), this->end()}.as_bytes()); } /** diff --git a/source/core/image.cpp b/source/coral/image.cpp similarity index 93% rename from source/core/image.cpp rename to source/coral/image.cpp index 5279c3f..049ccc4 100644 --- a/source/core/image.cpp +++ b/source/coral/image.cpp @@ -1,8 +1,8 @@ -export module core.image; +export module coral.image; -import core; +import coral; -export namespace core { +export namespace coral { /** * All-purpose color value for red, green, blue, alpha channel-encoded values. */ diff --git a/source/core/math.cpp b/source/coral/math.cpp similarity index 88% rename from source/core/math.cpp rename to source/coral/math.cpp index 157b43f..feb6a0e 100644 --- a/source/core/math.cpp +++ b/source/coral/math.cpp @@ -1,8 +1,8 @@ -export module core.math; +export module coral.math; -import core; +import coral; -export namespace core { +export namespace coral { /** * Two-component vector type backed by 32-bit floating point values. */ diff --git a/source/core/sequence.cpp b/source/coral/sequence.cpp similarity index 97% rename from source/core/sequence.cpp rename to source/coral/sequence.cpp index 316427b..49cd8c9 100644 --- a/source/core/sequence.cpp +++ b/source/coral/sequence.cpp @@ -1,8 +1,8 @@ -export module core.sequence; +export module coral.sequence; -import core; +import coral; -export namespace core { +export namespace coral { /** * Result codes used by [sequence]-derived types when they are appended to in any way. * @@ -179,7 +179,7 @@ export namespace core { return append_result::out_of_memory; } - core::copy({buffer, buffer_size}, this->elements.as_bytes()); + coral::copy({buffer, buffer_size}, this->elements.as_bytes()); this->elements = {reinterpret_cast(buffer), requested_capacity}; } diff --git a/source/kym.cpp b/source/kym.cpp index 3ac4528..7d43d11 100644 --- a/source/kym.cpp +++ b/source/kym.cpp @@ -1,7 +1,7 @@ export module kym; -import core; -import core.math; +import coral; +import coral.math; export namespace kym { enum class value_type { @@ -20,22 +20,22 @@ export namespace kym { union { bool boolean; - core::i64 integer; + coral::i64 integer; - core::f64 scalar; + coral::f64 scalar; - core::vector2 vector2; + coral::vector2 vector2; - core::vector3 vector3; + coral::vector3 vector3; void * object; } as; - core::optional as_u16() const { + coral::optional as_u16() const { if ((this->type == value_type::integer) && - (this->as.integer >= 0) && (this->as.integer <= core::u16_max)) { + (this->as.integer >= 0) && (this->as.integer <= coral::u16_max)) { - return static_cast(this->as.integer); + return static_cast(this->as.integer); } return {}; diff --git a/source/kym/environment.cpp b/source/kym/environment.cpp index 038e117..11ce35d 100644 --- a/source/kym/environment.cpp +++ b/source/kym/environment.cpp @@ -1,11 +1,11 @@ export module kym.environment; -import core; -import core.sequence; +import coral; +import coral.sequence; import kym; -using loggable = core::callable const &)>; +using loggable = coral::callable const &)>; export namespace kym { struct vm; @@ -16,18 +16,18 @@ enum class token_kind { }; struct token { - core::slice text; + coral::slice text; token_kind kind; }; struct tokenizer { - core::slice source; + coral::slice source; - tokenizer(core::slice const & source) : source{source} {} + tokenizer(coral::slice const & source) : source{source} {} token next() { - core::usize cursor = 0; + coral::usize cursor = 0; while (cursor < source.length) { @@ -40,7 +40,7 @@ struct tokenizer { }; struct bytecode { - bytecode(core::allocator * allocator) : error_message_buffer{allocator} { + bytecode(coral::allocator * allocator) : error_message_buffer{allocator} { } @@ -51,26 +51,26 @@ struct bytecode { switch (initial_token.kind) { case token_kind::end: return true; - default: core::unreachable(); + default: coral::unreachable(); } } } - kym::value execute(kym::vm & vm, core::slice const & arguments) { + kym::value execute(kym::vm & vm, coral::slice const & arguments) { return kym::nil; } private: - core::stack error_message_buffer; + coral::stack error_message_buffer; }; export namespace kym { - value default_call(vm & owning_vm, void * userdata, core::slice const & arguments) { + value default_call(vm & owning_vm, void * userdata, coral::slice const & arguments) { return nil; } - core::expected default_stringify(vm & owning_vm, void * userdata, core::writable const & output) { - return output(core::slice{"[object]"}.as_bytes()); + coral::expected default_stringify(vm & owning_vm, void * userdata, coral::writable const & output) { + return output(coral::slice{"[object]"}.as_bytes()); } struct bound_object { @@ -79,9 +79,9 @@ export namespace kym { struct { void(*cleanup)(vm &, void *); - value(*call)(vm &, void *, core::slice const &); + value(*call)(vm &, void *, coral::slice const &); - core::expected(*stringify)(vm &, void *, core::writable const &); + coral::expected(*stringify)(vm &, void *, coral::writable const &); } behavior; bound_object(vm * owning_vm) : userdata{nullptr}, owning_vm{owning_vm}, behavior{ @@ -94,15 +94,15 @@ export namespace kym { this->behavior.cleanup(*this->owning_vm, this->userdata); } - value call(core::slice const & arguments) { + value call(coral::slice const & arguments) { return this->behavior.call(*this->owning_vm, this->userdata, arguments); } - core::expected stringify(core::writable const & output) { + coral::expected stringify(coral::writable const & output) { return this->behavior.stringify(*this->owning_vm, this->userdata, output); } - value get_field(core::slice const & field_name) { + value get_field(coral::slice const & field_name) { return nil; } @@ -112,12 +112,12 @@ export namespace kym { struct vm { struct init_options { - core::u16 datastack_size; + coral::u16 datastack_size; - core::u16 callstack_size; + coral::u16 callstack_size; }; - vm(core::allocator * allocator, auto log) : allocator{allocator}, log{log}, data_stack{} {} + vm(coral::allocator * allocator, auto log) : allocator{allocator}, log{log}, data_stack{} {} ~vm() { if (this->data_stack.pointer != nullptr) @@ -125,8 +125,8 @@ export namespace kym { } bool init(init_options const & options) { - core::u8 * const data_stack_buffer = this->allocator->reallocate(reinterpret_cast - (this->data_stack.pointer), options.datastack_size * sizeof(value)); + coral::u8 * const data_stack_buffer = this->allocator->reallocate(reinterpret_cast + (this->data_stack.pointer), options.datastack_size * sizeof(value)); if (data_stack_buffer == nullptr) return false; @@ -136,12 +136,12 @@ export namespace kym { return true; } - value compile(core::slice const & source) { + value compile(coral::slice const & source) { bytecode * source_bytecode = new (*this->allocator) bytecode{allocator}; if (source_bytecode == nullptr) return nil; - if (!source_bytecode->compile(tokenizer{source}, [&](core::slice error_message) { + if (!source_bytecode->compile(tokenizer{source}, [&](coral::slice error_message) { this->log(error_message); })) { this->allocator->deallocate(source_bytecode); @@ -156,7 +156,7 @@ export namespace kym { owning_vm.allocator->deallocate(userdata); }; - object.behavior.call = [](vm & owning_vm, void * userdata, core::slice const & arguments) -> value { + object.behavior.call = [](vm & owning_vm, void * userdata, coral::slice const & arguments) -> value { return reinterpret_cast(userdata)->execute(owning_vm, arguments); }; @@ -164,19 +164,19 @@ export namespace kym { }); } - value new_object(core::callable const & then) { + value new_object(coral::callable const & then) { return nil; } - void with_object(value object_value, core::callable const & then) { + void with_object(value object_value, coral::callable const & then) { } private: - core::slice data_stack; + coral::slice data_stack; loggable log; - core::allocator * allocator; + coral::allocator * allocator; }; } diff --git a/source/oar.cpp b/source/oar.cpp index dacae13..2f93048 100644 --- a/source/oar.cpp +++ b/source/oar.cpp @@ -1,40 +1,40 @@ export module oar; -import core; -import core.files; +import coral; +import coral.files; export namespace oar { - constexpr core::usize signature_length{4}; + constexpr coral::usize signature_length{4}; - constexpr core::u8 signature_magic[signature_length]{'o', 'a', 'r', 0}; + constexpr coral::u8 signature_magic[signature_length]{'o', 'a', 'r', 0}; struct entry { - core::u8 signature_magic[signature_length]; + coral::u8 signature_magic[signature_length]; - core::path path; + coral::path path; - core::u64 data_offset; + coral::u64 data_offset; - core::u64 data_length; + coral::u64 data_length; - core::u8 padding[244]; + coral::u8 padding[244]; }; - struct archive : public core::fs { - using core::fs::access_result; + struct archive : public coral::fs { + using coral::fs::access_result; archive() { } - access_result read_file(core::path const & file_path, - core::callable const & then) override { + access_result read_file(coral::path const & file_path, + coral::callable const & then) override { return access_result::access_denied; } - virtual access_result write_file(core::path const & file_path, - core::callable const & then) override { + virtual access_result write_file(coral::path const & file_path, + coral::callable const & then) override { return access_result::access_denied; } diff --git a/source/runtime.cpp b/source/runtime.cpp index 24af2e5..1248c3a 100644 --- a/source/runtime.cpp +++ b/source/runtime.cpp @@ -2,22 +2,22 @@ export module runtime; import app; -import core; -import core.files; -import core.math; -import core.sequence; +import coral; +import coral.files; +import coral.math; +import coral.sequence; import kym; import kym.environment; extern "C" int main(int argc, char const * const * argv) { return app::display("Ona Runtime", [](app::system & system, app::graphics & graphics) -> int { - constexpr core::path config_path{"config.kym"}; + constexpr coral::path config_path{"config.kym"}; bool is_config_loaded{false}; - if ((system.res_fs().read_file(config_path, [&](core::readable const & file) { + if ((system.res_fs().read_file(config_path, [&](coral::readable const & file) { kym::vm vm{&system.thread_safe_allocator(), - [&system](core::slice const & error_message) { + [&system](coral::slice const & error_message) { system.log(app::log_level::error, error_message); }}; @@ -30,20 +30,20 @@ extern "C" int main(int argc, char const * const * argv) { return; } - core::stack script_source{&system.thread_safe_allocator()}; - core::u8 stream_buffer[1024]{0}; + coral::stack script_source{&system.thread_safe_allocator()}; + coral::u8 stream_buffer[1024]{0}; - if (!core::stream(core::sequence_writer{&script_source}, file, stream_buffer).is_ok()) + if (!coral::stream(coral::sequence_writer{&script_source}, file, stream_buffer).is_ok()) return; - vm.with_object(vm.compile(core::slice{script_source.begin(), script_source.end()}.as_chars()), [&](kym::bound_object & script) { + 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) { - core::u16 const width{config.get_field("width").as_u16().value_or(0)}; + coral::u16 const width{config.get_field("width").as_u16().value_or(0)}; if (width == 0) return system.log(app::log_level::error, "failed to decode `width` property of config"); - core::u16 const height{config.get_field("height").as_u16().value_or(0)}; + coral::u16 const height{config.get_field("height").as_u16().value_or(0)}; if (height == 0) return system.log(app::log_level::error, "failed to decode `height` property of config"); @@ -52,9 +52,9 @@ extern "C" int main(int argc, char const * const * argv) { return system.log(app::log_level::error, "failed to initialize window"); vm.with_object(config.get_field("title"), [&](kym::bound_object & title) { - core::stack title_buffer{&system.thread_safe_allocator()}; + coral::stack title_buffer{&system.thread_safe_allocator()}; - if (!title.stringify(core::sequence_writer(&title_buffer)).is_ok()) { + if (!title.stringify(coral::sequence_writer(&title_buffer)).is_ok()) { system.log(app::log_level::error, "failed to decode `title` property of config"); @@ -65,7 +65,7 @@ extern "C" int main(int argc, char const * const * argv) { }); }); }); - }) != core::fs::access_result::ok) || (!is_config_loaded)) return core::u8_max; + }) != coral::fs::access_result::ok) || (!is_config_loaded)) return coral::u8_max; // app::canvas canvas_2d(); From cba6e26995ae1b4660e3a4ec6c2ccd9df426e080 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 16:51:38 +0000 Subject: [PATCH 24/35] Remove unnecessary coral:: prefixes in coral libray --- source/coral.cpp | 2 +- source/coral/sequence.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/coral.cpp b/source/coral.cpp index 7d7b703..4d265c4 100644 --- a/source/coral.cpp +++ b/source/coral.cpp @@ -478,7 +478,7 @@ export namespace coral { * *Note*: safety-checked behavior is triggered if `target` is smaller than `origin`. */ void copy(slice const & target, slice const & origin) { - if (target.length < origin.length) coral::unreachable(); + if (target.length < origin.length) unreachable(); for (usize i = 0; i < origin.length; i += 1) target[i] = origin[i]; } diff --git a/source/coral/sequence.cpp b/source/coral/sequence.cpp index 49cd8c9..24258cc 100644 --- a/source/coral/sequence.cpp +++ b/source/coral/sequence.cpp @@ -179,7 +179,7 @@ export namespace coral { return append_result::out_of_memory; } - coral::copy({buffer, buffer_size}, this->elements.as_bytes()); + copy({buffer, buffer_size}, this->elements.as_bytes()); this->elements = {reinterpret_cast(buffer), requested_capacity}; } From 8ced8440fce6e0f2b51c4b66be1b222a5577e765 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 17:16:43 +0000 Subject: [PATCH 25/35] Remove coral::fs::access_result --- source/app.cpp | 52 +++++++++++++++++++----------------------- source/coral/files.cpp | 25 ++------------------ source/oar.cpp | 10 ++------ source/runtime.cpp | 11 ++++----- 4 files changed, 32 insertions(+), 66 deletions(-) diff --git a/source/app.cpp b/source/app.cpp index 64bd585..5cd51a1 100644 --- a/source/app.cpp +++ b/source/app.cpp @@ -13,8 +13,6 @@ import oar; export namespace app { struct directory : public coral::fs { - using coral::fs::access_result; - struct rules { bool can_read; @@ -23,24 +21,22 @@ export namespace app { directory() : path_buffer{0} {} - access_result read_file(coral::path const & file_path, + void read_file(coral::path const & file_path, coral::callable const & then) { - if (this->prefix_length == 0) return access_result::not_found; + if (this->prefix_length == 0) return; - if (!this->access_rules.can_read) return access_result::access_denied; + if (!this->access_rules.can_read) return; - ::SDL_RWops * rw_ops = this->open_rw(file_path, {.can_read = true}); + ::SDL_RWops * rw_ops{this->open_rw(file_path, {.can_read = true})}; - if (rw_ops == nullptr) return access_result::not_found; + 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); - - return access_result::ok; } void target(coral::slice const & directory_path, rules const & access_rules) { @@ -57,24 +53,22 @@ export namespace app { } } - access_result write_file(coral::path const & file_path, + void write_file(coral::path const & file_path, coral::callable const & then) { - if (this->prefix_length == 0) return access_result::not_found; + if (this->prefix_length == 0) return; - if (!this->access_rules.can_write) return access_result::access_denied; + if (!this->access_rules.can_write) return; - ::SDL_RWops * rw_ops = this->open_rw(file_path, {.can_write = true}); + ::SDL_RWops * rw_ops{this->open_rw(file_path, {.can_write = true})}; - if (rw_ops == nullptr) return access_result::not_found; + 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); - - return access_result::ok; } private: @@ -87,7 +81,7 @@ export namespace app { 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::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}; @@ -111,12 +105,12 @@ export namespace app { constexpr directory::rules read_only_rules = {.can_read = true}; { - char * const path = ::SDL_GetBasePath(); + char * const path{::SDL_GetBasePath()}; if (path == nullptr) { this->cwd_directory.target("./", read_only_rules); } else { - coral::usize path_length = 0; + coral::usize path_length{0}; while (path[path_length] != 0) path_length += 1; @@ -131,10 +125,10 @@ export namespace app { } { - char * const path = ::SDL_GetPrefPath("ona", title.begin()); + char * const path{::SDL_GetPrefPath("ona", title.begin())}; if (path != nullptr) { - coral::usize path_length = 0; + coral::usize path_length{0}; while (path[path_length] != 0) path_length += 1; @@ -166,8 +160,8 @@ export namespace app { } void log(app::log_level level, coral::slice const & message) { - coral::i32 const length = static_cast( - coral::min(message.length, static_cast(coral::i32_max))); + coral::i32 const length{static_cast( + coral::min(message.length, static_cast(coral::i32_max)))}; ::SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "%.*s", length, message.pointer); @@ -215,6 +209,12 @@ export namespace app { this->retitle(title); } + void present() { + if (this->sdl_renderer != nullptr) { + ::SDL_RenderPresent(this->sdl_renderer); + } + } + void render(canvas & source_canvas) { if (this->sdl_renderer != nullptr) { SDL_SetRenderDrawColor(this->sdl_renderer, source_canvas.background_color.to_r8(), @@ -257,12 +257,6 @@ export namespace app { return show_result::ok; } - void present() { - if (this->sdl_renderer != nullptr) { - ::SDL_RenderPresent(this->sdl_renderer); - } - } - private: coral::path title; diff --git a/source/coral/files.cpp b/source/coral/files.cpp index 67330d1..d583267 100644 --- a/source/coral/files.cpp +++ b/source/coral/files.cpp @@ -105,43 +105,22 @@ export namespace coral { * Platform-generalized file system interface. */ struct fs { - /** - * The result from a file access operation. - * - * [file_result::ok] indicates a successful file access operation. - * - * [file_result::not_found] signals that the file was not found. - * - * [file_result::access_denied] warns that the file was found but cannot be opened through - * the file system interface. The most common scenario for this error is a lack of required - * file permissions. - */ - enum class [[nodiscard]] access_result { - ok, - not_found, - access_denied, - }; - /** * Attempts to read the file in the file system located at `file_path` relative, calling * `then` if it was successfully opened for reading. * - * The returned [access_result] indicates whether the operation was successful or not. - * * Once `then` returns, access to the file is closed automatically. */ - virtual access_result read_file(path const & file_path, + virtual void read_file(path const & file_path, callable const & then) = 0; /** * Attempts to write the file in the file system located at `file_path` relative, calling * `then` if it was successfully opened for writing. * - * The returned [access_result] indicates whether the operation was successful or not. - * * Once `then` returns, access to the file is closed automatically. */ - virtual access_result write_file(path const & file_path, + virtual void write_file(path const & file_path, callable const & then) = 0; }; } diff --git a/source/oar.cpp b/source/oar.cpp index 2f93048..f2b3e5d 100644 --- a/source/oar.cpp +++ b/source/oar.cpp @@ -21,22 +21,16 @@ export namespace oar { }; struct archive : public coral::fs { - using coral::fs::access_result; - archive() { } - access_result read_file(coral::path const & file_path, + void read_file(coral::path const & file_path, coral::callable const & then) override { - - return access_result::access_denied; } - virtual access_result write_file(coral::path const & file_path, + void write_file(coral::path const & file_path, coral::callable const & then) override { - - return access_result::access_denied; } }; } diff --git a/source/runtime.cpp b/source/runtime.cpp index 1248c3a..acdf322 100644 --- a/source/runtime.cpp +++ b/source/runtime.cpp @@ -15,16 +15,13 @@ extern "C" int main(int argc, char const * const * argv) { constexpr coral::path config_path{"config.kym"}; bool is_config_loaded{false}; - if ((system.res_fs().read_file(config_path, [&](coral::readable const & file) { + system.res_fs().read_file(config_path, [&](coral::readable const & file) { kym::vm vm{&system.thread_safe_allocator(), [&system](coral::slice const & error_message) { system.log(app::log_level::error, error_message); }}; - if (!vm.init({ - .datastack_size = 64, - .callstack_size = 64, - })) { + if (!vm.init({.datastack_size = 64, .callstack_size = 64})) { system.log(app::log_level::error, "failed to allocate memory for config vm"); return; @@ -65,7 +62,9 @@ extern "C" int main(int argc, char const * const * argv) { }); }); }); - }) != coral::fs::access_result::ok) || (!is_config_loaded)) return coral::u8_max; + }); + + if (!is_config_loaded) return coral::u8_max; // app::canvas canvas_2d(); From 3856a03ec1ad2f6ca092fd3a10b2dc8c0b680d31 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 17:43:09 +0000 Subject: [PATCH 26/35] Add explicit conversion from coral::path to coral::slice --- source/app.cpp | 2 +- source/coral/files.cpp | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/source/app.cpp b/source/app.cpp index 5cd51a1..56352e6 100644 --- a/source/app.cpp +++ b/source/app.cpp @@ -88,7 +88,7 @@ export namespace app { if (path_remaining.length < file_path.byte_size()) return nullptr; - coral::copy(path_remaining, coral::slice{file_path.begin(), file_path.end()}.as_bytes()); + coral::copy(path_remaining, file_path.as_slice().as_bytes()); return ::SDL_RWFromFile(reinterpret_cast(this->path_buffer), "r"); } diff --git a/source/coral/files.cpp b/source/coral/files.cpp index d583267..2231794 100644 --- a/source/coral/files.cpp +++ b/source/coral/files.cpp @@ -29,6 +29,13 @@ export namespace coral { this->buffer[max] = max - text_size; } + /** + * Returns a weak reference to the [path] as a [slice]. + */ + constexpr slice as_slice() const { + return {this->buffer, this->byte_size()}; + } + /** * Returns the base pointer of the path name. * @@ -50,8 +57,7 @@ export namespace coral { * they are identical. */ constexpr size compare(path const & that) const { - return coral::compare(slice{this->begin(), this->end()}.as_bytes(), - slice{that.begin(), that.end()}.as_bytes()); + return coral::compare(this->as_slice().as_bytes(), that.as_slice().as_bytes()); } /** @@ -66,8 +72,7 @@ export namespace coral { * otherwise `false`. */ constexpr bool equals(path const & that) const { - return coral::equals(slice{this->begin(), this->end()}.as_bytes(), - slice{that.begin(), that.end()}.as_bytes()); + return coral::equals(this->as_slice().as_bytes(), that.as_slice().as_bytes()); } /** @@ -76,7 +81,7 @@ export namespace coral { * *Note:* the returned hash code is not guaranteed to be unique. */ constexpr u64 hash() const { - return coral::hash(slice{this->begin(), this->end()}.as_bytes()); + return coral::hash(this->as_slice().as_bytes()); } /** From c45a270a0b01d9887992e31c644a641f96eba1a8 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 17:43:37 +0000 Subject: [PATCH 27/35] Improve config load error message in runtime --- source/runtime.cpp | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/source/runtime.cpp b/source/runtime.cpp index acdf322..4bf31d7 100644 --- a/source/runtime.cpp +++ b/source/runtime.cpp @@ -16,10 +16,11 @@ extern "C" int main(int argc, char const * const * argv) { bool is_config_loaded{false}; system.res_fs().read_file(config_path, [&](coral::readable const & file) { - kym::vm vm{&system.thread_safe_allocator(), - [&system](coral::slice const & error_message) { - system.log(app::log_level::error, error_message); - }}; + coral::allocator * const allocator{&system.thread_safe_allocator()}; + + kym::vm vm{allocator, [&system](coral::slice const & error_message) { + system.log(app::log_level::error, error_message); + }}; if (!vm.init({.datastack_size = 64, .callstack_size = 64})) { system.log(app::log_level::error, "failed to allocate memory for config vm"); @@ -27,7 +28,7 @@ extern "C" int main(int argc, char const * const * argv) { return; } - coral::stack script_source{&system.thread_safe_allocator()}; + coral::stack script_source{allocator}; coral::u8 stream_buffer[1024]{0}; if (!coral::stream(coral::sequence_writer{&script_source}, file, stream_buffer).is_ok()) @@ -51,7 +52,7 @@ 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()}; - if (!title.stringify(coral::sequence_writer(&title_buffer)).is_ok()) { + if (!title.stringify(coral::sequence_writer{&title_buffer}).is_ok()) { system.log(app::log_level::error, "failed to decode `title` property of config"); @@ -64,7 +65,21 @@ extern "C" int main(int argc, char const * const * argv) { }); }); - if (!is_config_loaded) return coral::u8_max; + if (!is_config_loaded) { + coral::stack error_message{&system.thread_safe_allocator()}; + + { + coral::sequence_writer error_writer{&error_message}; + + if (!error_writer(coral::slice{"failed to load "}.as_bytes()).is_ok()) + return coral::u8_max; + + if (!error_writer(config_path.as_slice().as_bytes()).is_ok()) + return coral::u8_max; + } + + return coral::u8_max; + } // app::canvas canvas_2d(); From 9ac61b614fea6bafc984adc813e5464c0329f757 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 19 Feb 2023 23:06:17 +0000 Subject: [PATCH 28/35] Replace coral::readable / writable with coral::reader / writer --- source/coral/files.cpp | 20 ++++++++++++++++---- source/coral/sequence.cpp | 24 +++++++++++++++--------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/source/coral/files.cpp b/source/coral/files.cpp index 2231794..d9ba825 100644 --- a/source/coral/files.cpp +++ b/source/coral/files.cpp @@ -106,18 +106,31 @@ export namespace coral { char buffer[max + 1]; }; + struct file_reader : public reader { + virtual expected seek(u64 offset) = 0; + + virtual expected tell() = 0; + }; + + struct file_writer : public writer {}; + /** * Platform-generalized file system interface. */ struct fs { + struct io_rules { + bool can_read; + + bool can_write; + }; + /** * Attempts to read the file in the file system located at `file_path` relative, calling * `then` if it was successfully opened for reading. * * Once `then` returns, access to the file is closed automatically. */ - virtual void read_file(path const & file_path, - callable const & then) = 0; + virtual void read_file(path const & file_path, callable const & then) = 0; /** * Attempts to write the file in the file system located at `file_path` relative, calling @@ -125,7 +138,6 @@ export namespace coral { * * Once `then` returns, access to the file is closed automatically. */ - virtual void write_file(path const & file_path, - callable const & then) = 0; + virtual void write_file(path const & file_path, callable const & then) = 0; }; } diff --git a/source/coral/sequence.cpp b/source/coral/sequence.cpp index 24258cc..b568637 100644 --- a/source/coral/sequence.cpp +++ b/source/coral/sequence.cpp @@ -200,14 +200,20 @@ export namespace coral { /** * Writable type for appending data to a [sequence] containing [u8] values. */ - struct sequence_writer : public writable { - sequence_writer(sequence * output_sequence) : writable{ - [output_sequence](slice const & buffer) -> expected { - switch (output_sequence->append(buffer)) { - case append_result::ok: return buffer.length; - case append_result::out_of_memory: return io_error::unavailable; - default: unreachable(); - } - }} {} + struct sequence_writer : public writer { + sequence_writer(sequence * output_sequence) { + this->output_sequence = output_sequence; + } + + expected write(slice const & buffer) { + switch (output_sequence->append(buffer)) { + case append_result::ok: return buffer.length; + case append_result::out_of_memory: return io_error::unavailable; + default: unreachable(); + } + } + + private: + sequence * output_sequence; }; } From 924780d92c148c8a76936d17599e64ab9d204860 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 20 Feb 2023 00:34:46 +0000 Subject: [PATCH 29/35] Update coral library to use new reader / writer system --- source/coral.cpp | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/source/coral.cpp b/source/coral.cpp index 4d265c4..6e2826f 100644 --- a/source/coral.cpp +++ b/source/coral.cpp @@ -450,9 +450,13 @@ export namespace coral { unavailable, }; - using readable = callable(slice const &)>; + struct reader { + virtual expected read(slice const & buffer) = 0; + }; - using writable = callable(slice const &)>; + struct writer { + virtual expected write(slice const & buffer) = 0; + }; } // Input/output operations. @@ -533,30 +537,28 @@ export namespace coral { * *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(writable const & output, - readable const & input, slice const & buffer) { + expected stream(writer & output, reader & input, slice const & buffer) { + usize total_bytes_written = 0; + expected bytes_read = input.read(buffer); - usize written = 0; - expected maybe_read = input(buffer); + if (!bytes_read.is_ok()) return bytes_read.error(); - if (!maybe_read.is_ok()) return maybe_read.error(); - - usize read = maybe_read.value(); + usize read = bytes_read.value(); while (read != 0) { - expected const maybe_written = output(buffer.sliced(0, read)); + expected const bytes_written = output.write(buffer.sliced(0, read)); - if (!maybe_written.is_ok()) return maybe_read.error(); + if (!bytes_written.is_ok()) return bytes_read.error(); - written += maybe_written.value(); - maybe_read = input(buffer); + total_bytes_written += bytes_written.value(); + bytes_read = input.read(buffer); - if (!maybe_read.is_ok()) return maybe_read.error(); + if (!bytes_read.is_ok()) return bytes_read.error(); - read = maybe_read.value(); + read = bytes_read.value(); } - return written; + return total_bytes_written; } /** @@ -565,8 +567,8 @@ export namespace coral { * 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(writable const & output, u64 value) { - if (value == 0) return output(slice{"0"}.as_bytes()); + 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}; @@ -584,7 +586,7 @@ export namespace coral { for (usize i = 0; i < half_buffer_count; i += 1) swap(buffer[i], buffer[buffer_count - i - 1]); - return output(slice{buffer, buffer_count}); + return output.write(slice{buffer, buffer_count}); } /** From 3e4753f9855c8b916e6ae2a20ee5d11b5e91170e Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 20 Feb 2023 00:35:29 +0000 Subject: [PATCH 30/35] Fix incorrect type aliases in coral library --- source/coral.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/coral.cpp b/source/coral.cpp index 6e2826f..3e530e5 100644 --- a/source/coral.cpp +++ b/source/coral.cpp @@ -42,9 +42,9 @@ export namespace coral { usize const i32_max = 0xffffffff; - using u64 = uint32_t; + using u64 = uint64_t; - using i64 = uint32_t; + using i64 = uint64_t; using f32 = float; From a85b0c21f501d1b7cea1cfc070ca67619903d9c4 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 20 Feb 2023 00:49:35 +0000 Subject: [PATCH 31/35] Expose IO rules on the file system interface --- source/coral/files.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/coral/files.cpp b/source/coral/files.cpp index d9ba825..181ccf9 100644 --- a/source/coral/files.cpp +++ b/source/coral/files.cpp @@ -132,6 +132,11 @@ 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() = 0; + /** * Attempts to write the file in the file system located at `file_path` relative, calling * `then` if it was successfully opened for writing. From f76d908325a3589c350640da2159c02d6a71d71e Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 20 Feb 2023 01:00:14 +0000 Subject: [PATCH 32/35] Expose virtual destructor on coral::fs --- source/coral/files.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/coral/files.cpp b/source/coral/files.cpp index 181ccf9..ad58a04 100644 --- a/source/coral/files.cpp +++ b/source/coral/files.cpp @@ -124,6 +124,8 @@ export namespace coral { bool can_write; }; + virtual ~fs() = 0; + /** * Attempts to read the file in the file system located at `file_path` relative, calling * `then` if it was successfully opened for reading. From eb93da95e55676c2153eca83aeb1614732d274d9 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 20 Feb 2023 01:08:39 +0000 Subject: [PATCH 33/35] Change coral::fs::~fs to be non-pure virtual --- source/coral/files.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/coral/files.cpp b/source/coral/files.cpp index ad58a04..96536ba 100644 --- a/source/coral/files.cpp +++ b/source/coral/files.cpp @@ -124,7 +124,7 @@ export namespace coral { bool can_write; }; - virtual ~fs() = 0; + virtual ~fs() {}; /** * Attempts to read the file in the file system located at `file_path` relative, calling From 712b490894ffbe9d89196bd41a2fb3e7685a7b23 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 20 Feb 2023 01:10:17 +0000 Subject: [PATCH 34/35] Mark coral::fs::rules as const --- source/coral/files.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/coral/files.cpp b/source/coral/files.cpp index 96536ba..8a59106 100644 --- a/source/coral/files.cpp +++ b/source/coral/files.cpp @@ -137,7 +137,7 @@ export namespace coral { /** * Returns the [io_rules] that this file system interface abides by. */ - virtual io_rules rules() = 0; + virtual io_rules rules() const = 0; /** * Attempts to write the file in the file system located at `file_path` relative, calling From 168d5375a44cb5a156b327670a6aee790ad18938 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 20 Feb 2023 01:28:58 +0000 Subject: [PATCH 35/35] 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; }