Clean up core library.
This commit is contained in:
parent
1261ee7e60
commit
e3c4ab65f1
|
@ -2,7 +2,7 @@ export module app;
|
||||||
|
|
||||||
import core;
|
import core;
|
||||||
import core.image;
|
import core.image;
|
||||||
import core.math;
|
import core.lalgebra;
|
||||||
|
|
||||||
export namespace app {
|
export namespace app {
|
||||||
struct path {
|
struct path {
|
||||||
|
@ -32,8 +32,8 @@ export namespace app {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool equals(path const & that) {
|
bool equals(path const & that) const {
|
||||||
return core::equals(this->as_slice(), that.as_slice());
|
return core::equals(this->as_slice().as_bytes(), that.as_slice().as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr path joined(core::slice<char const> const & text) const {
|
constexpr path joined(core::slice<char const> const & text) const {
|
||||||
|
|
|
@ -9,19 +9,24 @@ import app;
|
||||||
import core;
|
import core;
|
||||||
import core.image;
|
import core.image;
|
||||||
import core.sequence;
|
import core.sequence;
|
||||||
import core.math;
|
import core.lalgebra;
|
||||||
|
|
||||||
struct bundled_file_store : public app::file_store {
|
struct bundled_file_store : public app::file_store {
|
||||||
void read_file(app::path const & file_path, core::callable<void(core::readable const &)> const & then) override {
|
void read_file(app::path const & file_path, core::callable<void(core::readable const &)> const & then) override {
|
||||||
// Path is guaranteed to never be greater than 512 characters long (file_path is max 256 and prefix is 2).
|
constexpr core::slice<char const> path_prefix = "./";
|
||||||
core::stack<char, 512> path_buffer{&core::null_allocator()};
|
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<char, path_max> path_buffer{&core::null_allocator()};
|
||||||
|
|
||||||
|
if (path_buffer.append("./") != core::append_result::ok) core::unreachable();
|
||||||
|
|
||||||
// File path is guaranteed to be null-terminated.
|
// 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;
|
if (rw_ops == nullptr) return;
|
||||||
|
|
||||||
|
|
473
source/core.cpp
473
source/core.cpp
|
@ -5,6 +5,20 @@ module;
|
||||||
|
|
||||||
export module core;
|
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 {
|
export namespace core {
|
||||||
using usize = size_t;
|
using usize = size_t;
|
||||||
|
|
||||||
|
@ -36,118 +50,230 @@ export namespace core {
|
||||||
|
|
||||||
using f64 = double;
|
using f64 = double;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base type for runtime-pluggable memory allocation strategies used by the core library.
|
||||||
|
*/
|
||||||
struct allocator {
|
struct allocator {
|
||||||
allocator() = default;
|
|
||||||
|
|
||||||
allocator(allocator const &) = delete;
|
|
||||||
|
|
||||||
virtual ~allocator() {};
|
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;
|
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<typename scalar> constexpr scalar max(scalar const & a, scalar const & b) {
|
|
||||||
return (a > b) ? a : b;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename scalar> constexpr scalar min(scalar const & a, scalar const & b) {
|
|
||||||
return (a < b) ? a : b;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename scalar> 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<typename type> struct slice {
|
template<typename type> struct slice {
|
||||||
|
/**
|
||||||
|
* Number of `type` elements referenced.
|
||||||
|
*/
|
||||||
usize length;
|
usize length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base element address referenced.
|
||||||
|
*/
|
||||||
type * pointer;
|
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;
|
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<usize array_size> 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<usize>(slice_end - slice_begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<usize array_size> 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<u8 const> as_bytes() const {
|
slice<u8 const> as_bytes() const {
|
||||||
return {reinterpret_cast<u8 const *>(this->pointer), this->length * sizeof(type)};
|
return {reinterpret_cast<u8 const *>(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<char const> as_chars() const {
|
slice<char const> as_chars() const {
|
||||||
return {reinterpret_cast<char const *>(this->pointer), this->length * sizeof(type)};
|
return {reinterpret_cast<char const *>(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<type const>() const {
|
operator slice<type const>() const {
|
||||||
return (*reinterpret_cast<slice<type const> const *>(this));
|
return (*reinterpret_cast<slice<type const> const *>(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
type & operator[](usize index) const {
|
constexpr type & operator[](usize index) const {
|
||||||
|
if (this->length <= index) core::unreachable();
|
||||||
|
|
||||||
return this->pointer[index];
|
return this->pointer[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
slice<type> after(usize index) const {
|
|
||||||
return {this->pointer + index, this->length - index};
|
|
||||||
}
|
|
||||||
|
|
||||||
slice<type> until(usize index) const {
|
|
||||||
return {this->pointer, index};
|
|
||||||
}
|
|
||||||
|
|
||||||
slice<type> 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<typename scalar> constexpr scalar max(scalar const & a, scalar const & b) {
|
||||||
|
return (a > b) ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minimum value between `a` and `b`.
|
||||||
|
*/
|
||||||
|
template<typename scalar> 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<typename scalar> 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<core::u8> 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<core::u8> 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<typename element> struct [[nodiscard]] optional {
|
template<typename element> struct [[nodiscard]] optional {
|
||||||
optional() : buffer{0} {
|
optional() : buffer{0} {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
optional(element const & value) : buffer{0} {
|
optional(element const & value) : buffer{0} {
|
||||||
(*reinterpret_cast<element *>(this->buffer)) = value;
|
(*reinterpret_cast<element *>(this->buffer)) = value;
|
||||||
|
@ -163,18 +289,38 @@ export namespace core {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if the optional contains a value, otherwise `false`.
|
||||||
|
*/
|
||||||
bool has_value() const {
|
bool has_value() const {
|
||||||
return this->buffer[sizeof(element)] == 1;
|
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 {
|
element const & value_or(element const & fallback) const {
|
||||||
return this->has_value() ? *reinterpret_cast<element const *>(this->buffer) : fallback;
|
return this->has_value() ? *reinterpret_cast<element const *>(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() {
|
element & value() {
|
||||||
|
if (!this->has_value()) unreachable();
|
||||||
|
|
||||||
return *reinterpret_cast<element *>(this->buffer);
|
return *reinterpret_cast<element *>(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 {
|
element const & value() const {
|
||||||
return *reinterpret_cast<element const *>(this->buffer);
|
return *reinterpret_cast<element const *>(this->buffer);
|
||||||
}
|
}
|
||||||
|
@ -183,6 +329,9 @@ export namespace core {
|
||||||
u8 buffer[sizeof(element) + 1];
|
u8 buffer[sizeof(element) + 1];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monadic container for a descriminating union of either `value_element` or `error_element`.
|
||||||
|
*/
|
||||||
template<typename value_element, typename error_element> struct [[nodiscard]] expected {
|
template<typename value_element, typename error_element> struct [[nodiscard]] expected {
|
||||||
expected(value_element const & value) : buffer{0} {
|
expected(value_element const & value) : buffer{0} {
|
||||||
(*reinterpret_cast<value_element *>(this->buffer)) = value;
|
(*reinterpret_cast<value_element *>(this->buffer)) = value;
|
||||||
|
@ -193,28 +342,55 @@ export namespace core {
|
||||||
(*reinterpret_cast<error_element *>(this->buffer)) = error;
|
(*reinterpret_cast<error_element *>(this->buffer)) = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if the optional contains a value, otherwise `false` if it holds an error.
|
||||||
|
*/
|
||||||
bool is_ok() const {
|
bool is_ok() const {
|
||||||
return this->buffer[buffer_size];
|
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() {
|
value_element & value() {
|
||||||
if (!this->is_ok()) unreachable();
|
if (!this->is_ok()) unreachable();
|
||||||
|
|
||||||
return *reinterpret_cast<value_element *>(this->buffer);
|
return *reinterpret_cast<value_element *>(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 {
|
value_element const & value() const {
|
||||||
if (!this->is_ok()) unreachable();
|
if (!this->is_ok()) unreachable();
|
||||||
|
|
||||||
return *reinterpret_cast<value_element const *>(this->buffer);
|
return *reinterpret_cast<value_element const *>(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() {
|
error_element & error() {
|
||||||
if (this->is_ok()) unreachable();
|
if (this->is_ok()) unreachable();
|
||||||
|
|
||||||
return *reinterpret_cast<error_element *>(this->buffer);
|
return *reinterpret_cast<error_element *>(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 {
|
error_element const & error() const {
|
||||||
if (this->is_ok()) unreachable();
|
if (this->is_ok()) unreachable();
|
||||||
|
|
||||||
|
@ -229,39 +405,120 @@ export namespace core {
|
||||||
|
|
||||||
template<typename> struct callable;
|
template<typename> struct callable;
|
||||||
|
|
||||||
template<typename return_type, typename... argument_types> struct callable<return_type(argument_types...)> {
|
/**
|
||||||
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<typename return_value, typename... argument_values> struct callable<return_value(argument_values...)> {
|
||||||
|
using function = return_value(*)(argument_values...);
|
||||||
|
|
||||||
|
callable(function callable_function) {
|
||||||
|
this->dispatcher = [](u8 const * userdata, argument_values... arguments) -> return_value {
|
||||||
|
return (*reinterpret_cast<function const *>(userdata))(arguments...);
|
||||||
|
};
|
||||||
|
|
||||||
callable(function callable_function) : dispatcher(dispatch_function) {
|
|
||||||
new (this->capture) function{callable_function};
|
new (this->capture) function{callable_function};
|
||||||
}
|
}
|
||||||
|
|
||||||
callable(callable const &) = delete;
|
callable(callable const &) = delete;
|
||||||
|
|
||||||
template<typename functor> callable(functor const & callable_functor) : dispatcher(dispatch_functor<functor>) {
|
template<typename functor> callable(functor const & callable_functor) {
|
||||||
|
this->dispatcher = [](u8 const * userdata, argument_values... arguments) -> return_value {
|
||||||
|
return (*reinterpret_cast<functor const*>(userdata))(arguments...);
|
||||||
|
};
|
||||||
|
|
||||||
new (this->capture) functor{callable_functor};
|
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...);
|
return this->dispatcher(this->capture, arguments...);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr usize capture_size = 24;
|
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];
|
u8 capture[capture_size];
|
||||||
|
|
||||||
static return_type dispatch_function(u8 const * userdata, argument_types... arguments) {
|
|
||||||
return (*reinterpret_cast<function const *>(userdata))(arguments...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename functor_type> static return_type dispatch_functor(u8 const * userdata, argument_types... arguments) {
|
|
||||||
return (*reinterpret_cast<functor_type const*>(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<expected<usize, io_error>(slice<u8> const &)>;
|
||||||
|
|
||||||
|
using writable = callable<expected<usize, io_error>(slice<u8 const> 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<u8 const> const & a, slice<u8 const> 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<u8> const & target, slice<u8 const> 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<usize, io_error> stream(writable const & output,
|
||||||
|
readable const & input, slice<u8> 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() {
|
allocator & null_allocator() {
|
||||||
static struct : public allocator {
|
static struct : public allocator {
|
||||||
u8 * reallocate(u8 * maybe_allocation, usize requested_size) override {
|
u8 * reallocate(u8 * maybe_allocation, usize requested_size) override {
|
||||||
|
@ -277,44 +534,4 @@ export namespace core {
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class io_error {
|
|
||||||
unavailable,
|
|
||||||
};
|
|
||||||
|
|
||||||
using readable = callable<expected<usize, io_error>(slice<u8> const &)>;
|
|
||||||
|
|
||||||
using writable = callable<expected<usize, io_error>(slice<u8 const> const &)>;
|
|
||||||
|
|
||||||
template<typename element_type> bool equals(slice<element_type const> const & a, slice<element_type const> 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<usize, io_error> stream(writable const & output, readable const & input, slice<u8> 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,27 +3,54 @@ export module core.image;
|
||||||
import core;
|
import core;
|
||||||
|
|
||||||
export namespace core {
|
export namespace core {
|
||||||
|
/**
|
||||||
|
* All-purpose color value for red, green, blue, alpha channel-encoded values.
|
||||||
|
*/
|
||||||
struct color {
|
struct color {
|
||||||
|
/**
|
||||||
|
* Red channel.
|
||||||
|
*/
|
||||||
float r;
|
float r;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Green channel.
|
||||||
|
*/
|
||||||
float g;
|
float g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blue channel.
|
||||||
|
*/
|
||||||
float b;
|
float b;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alpha channel.
|
||||||
|
*/
|
||||||
float a;
|
float a;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Red channel represented in an 8-bit unsigned value.
|
||||||
|
*/
|
||||||
u8 to_r8() const {
|
u8 to_r8() const {
|
||||||
return static_cast<u8>(round32(clamp(this->r, 0.0f, 1.0f) * u8_max));
|
return static_cast<u8>(round32(clamp(this->r, 0.0f, 1.0f) * u8_max));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Green channel represented in an 8-bit unsigned value.
|
||||||
|
*/
|
||||||
u8 to_g8() const {
|
u8 to_g8() const {
|
||||||
return static_cast<u8>(round32(clamp(this->g, 0.0f, 1.0f) * u8_max));
|
return static_cast<u8>(round32(clamp(this->g, 0.0f, 1.0f) * u8_max));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blue channel represented in an 8-bit unsigned value.
|
||||||
|
*/
|
||||||
u8 to_b8() const {
|
u8 to_b8() const {
|
||||||
return static_cast<u8>(round32(clamp(this->b, 0.0f, 1.0f) * u8_max));
|
return static_cast<u8>(round32(clamp(this->b, 0.0f, 1.0f) * u8_max));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alpha channel represented in an 8-bit unsigned value.
|
||||||
|
*/
|
||||||
u8 to_a8() const {
|
u8 to_a8() const {
|
||||||
return static_cast<u8>(round32(clamp(this->a, 0.0f, 1.0f) * u8_max));
|
return static_cast<u8>(round32(clamp(this->a, 0.0f, 1.0f) * u8_max));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
|
@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -3,116 +3,212 @@ export module core.sequence;
|
||||||
import core;
|
import core;
|
||||||
|
|
||||||
export namespace 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,
|
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<typename element> struct sequence {
|
template<typename element> struct sequence {
|
||||||
sequence() = default;
|
|
||||||
|
|
||||||
sequence(sequence const &) = delete;
|
|
||||||
|
|
||||||
virtual ~sequence() {};
|
virtual ~sequence() {};
|
||||||
|
|
||||||
virtual slice<element const> as_slice() const = 0;
|
/**
|
||||||
|
* Attempts to append `source_elements` to the sequence.
|
||||||
virtual optional<reserve_error> append(slice<element const> const & appended_elements) = 0;
|
*
|
||||||
|
* 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<element const> const & source_elements) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename element, usize initial_capacity = 1> struct stack : public sequence<element> {
|
/**
|
||||||
stack(allocator * buffer_allocator) : filled{0},
|
* Last-in-first-out linear sequence of `element` values.
|
||||||
buffer{0}, buffer_allocator{buffer_allocator} {
|
*
|
||||||
|
* [stack] types will default to using an inline array of `init_capacity` at first. After all
|
||||||
this->elements = this->buffer;
|
* 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<typename element, usize init_capacity = 1> struct stack : public sequence<element> {
|
||||||
|
stack(allocator * dynamic_allocator) : local_buffer{0} {
|
||||||
|
this->dynamic_allocator = dynamic_allocator;
|
||||||
|
this->filled = 0;
|
||||||
|
this->elements = this->local_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
~stack() override {
|
~stack() override {
|
||||||
if (this->elements.pointer != this->buffer)
|
if (this->is_dynamic()) this->dynamic_allocator->deallocate(this->elements.pointer);
|
||||||
this->buffer_allocator->deallocate(this->elements.pointer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
slice<element const> as_slice() const override {
|
/**
|
||||||
return this->elements;
|
* Attempts to append `source_elements` to the top of the stack.
|
||||||
}
|
*
|
||||||
|
* The returned [append_result] indicates whether the operation was successful or not.
|
||||||
optional<reserve_error> push(element const & pushed_element) {
|
*
|
||||||
if (this->filled == this->elements.length) {
|
* If the returned [append_result] is anything but [append_result::ok], the stack will be
|
||||||
optional const maybe_error = this->reserve(this->elements.length);
|
* be left in an empty but valid state.
|
||||||
|
*
|
||||||
if (maybe_error.has_value()) return maybe_error;
|
* *Note* that [push] is recommended when appending singular values.
|
||||||
}
|
*/
|
||||||
|
append_result append(slice<element const> const & source_elements) override {
|
||||||
this->elements[this->filled] = pushed_element;
|
usize const updated_fill = this->filled + source_elements.length;
|
||||||
this->filled += 1;
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
optional<reserve_error> append(slice<element const> const & pushed_elements) override {
|
|
||||||
usize const updated_fill = this->filled + pushed_elements.length;
|
|
||||||
|
|
||||||
if (updated_fill >= this->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)
|
for (usize i = 0; i < source_elements.length; i += 1)
|
||||||
this->elements[this->filled + i] = pushed_elements[i];
|
this->elements[this->filled + i] = source_elements[i];
|
||||||
|
|
||||||
this->filled = updated_fill;
|
this->filled = updated_fill;
|
||||||
|
|
||||||
return {};
|
return append_result::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<reserve_error> 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;
|
usize const requested_capacity = this->elements.length + capacity;
|
||||||
|
|
||||||
if (this->elements.pointer == this->buffer) {
|
if (this->is_dynamic()) {
|
||||||
u8 * const maybe_allocation = this->buffer_allocator->
|
// Grow dynamic buffer (bailing out if failed).
|
||||||
reallocate(nullptr, sizeof(element) * requested_capacity);
|
u8 * const buffer = this->dynamic_allocator->reallocate(
|
||||||
|
|
||||||
if (maybe_allocation == nullptr) {
|
|
||||||
this->elements = {};
|
|
||||||
|
|
||||||
return reserve_error::out_of_memory;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->elements = {reinterpret_cast<element *>(maybe_allocation), requested_capacity};
|
|
||||||
} else {
|
|
||||||
u8 * const maybe_allocation = this->buffer_allocator->reallocate(
|
|
||||||
reinterpret_cast<u8 *>(this->elements.pointer),
|
reinterpret_cast<u8 *>(this->elements.pointer),
|
||||||
sizeof(element) * requested_capacity);
|
sizeof(element) * requested_capacity);
|
||||||
|
|
||||||
if (maybe_allocation == nullptr) {
|
if (buffer == nullptr) {
|
||||||
this->elements = {};
|
this->elements = {};
|
||||||
|
|
||||||
return reserve_error::out_of_memory;
|
return append_result::out_of_memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->elements = {reinterpret_cast<element *>(maybe_allocation), requested_capacity};
|
this->elements = {reinterpret_cast<element *>(buffer), requested_capacity};
|
||||||
|
} else {
|
||||||
|
usize const buffer_size = sizeof(element) * requested_capacity;
|
||||||
|
u8 * const buffer = this->dynamic_allocator->reallocate(nullptr, buffer_size);
|
||||||
|
|
||||||
|
if (buffer == nullptr) {
|
||||||
|
this->elements = {};
|
||||||
|
|
||||||
|
return append_result::out_of_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
core::copy({buffer, buffer_size}, this->elements.as_bytes());
|
||||||
|
|
||||||
|
this->elements = {reinterpret_cast<element *>(buffer), requested_capacity};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return append_result::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
allocator * buffer_allocator;
|
allocator * dynamic_allocator;
|
||||||
|
|
||||||
usize filled;
|
usize filled;
|
||||||
|
|
||||||
slice<element> elements;
|
slice<element> 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 {
|
struct sequence_writer : public writable {
|
||||||
sequence_writer(sequence<u8> * target_sequence) : writable{[target_sequence](slice<u8 const> const & buffer) -> expected<usize, io_error> {
|
sequence_writer(sequence<u8> * output_sequence) : writable{
|
||||||
optional const maybe_error = target_sequence->append(buffer);
|
[output_sequence](slice<u8 const> const & buffer) -> expected<usize, io_error> {
|
||||||
|
switch (output_sequence->append(buffer)) {
|
||||||
if (maybe_error.has_value()) return io_error::unavailable;
|
case append_result::ok: return buffer.length;
|
||||||
|
case append_result::out_of_memory: return io_error::unavailable;
|
||||||
return buffer.length;
|
default: unreachable();
|
||||||
}} {}
|
}
|
||||||
|
}} {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export module kym;
|
export module kym;
|
||||||
|
|
||||||
import core;
|
import core;
|
||||||
import core.math;
|
import core.lalgebra;
|
||||||
|
|
||||||
export namespace kym {
|
export namespace kym {
|
||||||
enum class value_type {
|
enum class value_type {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import core.sequence;
|
||||||
|
|
||||||
import kym;
|
import kym;
|
||||||
|
|
||||||
|
using loggable = core::callable<void(core::slice<char const> const &)>;
|
||||||
|
|
||||||
export namespace kym {
|
export namespace kym {
|
||||||
struct vm;
|
struct vm;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +44,7 @@ struct bytecode {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool compile(tokenizer bytecode_tokenizer) {
|
bool compile(tokenizer bytecode_tokenizer, loggable const & log_error) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
token const initial_token = bytecode_tokenizer.next();
|
token const initial_token = bytecode_tokenizer.next();
|
||||||
|
|
||||||
|
@ -58,10 +60,6 @@ struct bytecode {
|
||||||
return kym::nil;
|
return kym::nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
core::slice<char const> error_message() const {
|
|
||||||
return this->error_message_buffer.as_slice();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
core::stack<char> error_message_buffer;
|
core::stack<char> error_message_buffer;
|
||||||
};
|
};
|
||||||
|
@ -71,8 +69,8 @@ export namespace kym {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
core::expected<core::usize, core::io_error> default_stringify(vm & owning_vm, void * userdata, core::writable const & writable) {
|
core::expected<core::usize, core::io_error> default_stringify(vm & owning_vm, void * userdata, core::writable const & output) {
|
||||||
return writable(core::slice("[object]").as_bytes());
|
return output(core::slice("[object]").as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
struct bound_object {
|
struct bound_object {
|
||||||
|
@ -100,8 +98,8 @@ export namespace kym {
|
||||||
return this->behavior.call(*this->owning_vm, this->userdata, arguments);
|
return this->behavior.call(*this->owning_vm, this->userdata, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
core::expected<core::usize, core::io_error> stringify(core::writable const & writable) {
|
core::expected<core::usize, core::io_error> stringify(core::writable const & output) {
|
||||||
return this->behavior.stringify(*this->owning_vm, this->userdata, writable);
|
return this->behavior.stringify(*this->owning_vm, this->userdata, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
value get_field(core::slice<char const> const & field_name) {
|
value get_field(core::slice<char const> const & field_name) {
|
||||||
|
@ -143,8 +141,9 @@ export namespace kym {
|
||||||
|
|
||||||
if (source_bytecode == nullptr) return nil;
|
if (source_bytecode == nullptr) return nil;
|
||||||
|
|
||||||
if (source_bytecode->compile(tokenizer{source})) {
|
if (!source_bytecode->compile(tokenizer{source}, [&](core::slice<char const> error_message) {
|
||||||
this->log(source_bytecode->error_message());
|
this->log(error_message);
|
||||||
|
})) {
|
||||||
this->allocator->deallocate(source_bytecode);
|
this->allocator->deallocate(source_bytecode);
|
||||||
|
|
||||||
return nil;
|
return nil;
|
||||||
|
@ -176,7 +175,7 @@ export namespace kym {
|
||||||
private:
|
private:
|
||||||
core::slice<value> data_stack;
|
core::slice<value> data_stack;
|
||||||
|
|
||||||
core::callable<void(core::slice<char const>)> log;
|
loggable log;
|
||||||
|
|
||||||
core::allocator * allocator;
|
core::allocator * allocator;
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ import app;
|
||||||
import app.sdl;
|
import app.sdl;
|
||||||
|
|
||||||
import core;
|
import core;
|
||||||
import core.math;
|
import core.lalgebra;
|
||||||
import core.sequence;
|
import core.sequence;
|
||||||
|
|
||||||
import kym;
|
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");
|
constexpr app::path config_path = app::path::empty().joined("config.kym");
|
||||||
bool is_config_loaded = false;
|
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<char const> const & error_message) {
|
kym::vm vm{&system.thread_safe_allocator(), [&system](core::slice<char const> const & error_message) {
|
||||||
system.log(app::log_level::error, 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
core::stack<core::u8> config_source{&system.thread_safe_allocator()};
|
core::stack<core::u8> script_source{&system.thread_safe_allocator()};
|
||||||
core::u8 config_source_stream_buffer[1024] = {};
|
core::u8 stream_buffer[1024] = {};
|
||||||
|
|
||||||
if (!core::stream(core::sequence_writer{&config_source},
|
if (!core::stream(core::sequence_writer{&script_source}, file, stream_buffer).is_ok())
|
||||||
config_readable, config_source_stream_buffer).is_ok()) return;
|
return;
|
||||||
|
|
||||||
vm.with_object(vm.compile(config_source.as_slice().as_chars()), [&](kym::bound_object & config_script) {
|
vm.with_object(vm.compile(core::slice{script_source.begin(), script_source.end()}.as_chars()), [&](kym::bound_object & script) {
|
||||||
vm.with_object(config_script.call({}), [&](kym::bound_object & config) {
|
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,
|
if (width == 0) return system.log(app::log_level::error,
|
||||||
|
|
Loading…
Reference in New Issue