ona/source/app.cpp

318 lines
7.6 KiB
C++

module;
#include <SDL2/SDL.h>
export module app;
import coral;
import coral.files;
import coral.image;
import coral.io;
import coral.math;
import oar;
using native_path = coral::fixed_buffer<4096>;
struct native_file : public coral::file_reader, public coral::file_writer {
enum class open_mode {
read_only,
overwrite,
};
enum class [[nodiscard]] close_result {
ok,
io_unavailable,
};
enum class [[nodiscard]] open_result {
ok,
io_unavailable,
access_denied,
not_found,
};
native_file() = default;
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(native_path const & file_path, open_mode mode) {
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();
}
// No room for zero terminator.
if (file_path.is_full()) return open_result::not_found;
switch (mode) {
case open_mode::read_only: {
this->rw_ops = SDL_RWFromFile(file_path.as_slice().as_chars().begin(), "rb");
break;
}
case open_mode::overwrite: {
this->rw_ops = SDL_RWFromFile(file_path.as_slice().as_chars().begin(), "wb");
break;
}
default: coral::unreachable();
}
if (this->rw_ops == nullptr) return open_result::not_found;
return open_result::ok;
}
coral::expected<coral::usize, coral::io_error> read(coral::slice<coral::u8> const & data) override {
if (!this->is_open()) return coral::io_error::unavailable;
coral::usize const data_read{SDL_RWread(this->rw_ops, data.pointer, sizeof(uint8_t), data.length)};
if ((data_read == 0) && (SDL_GetError() != nullptr)) return coral::io_error::unavailable;
return data_read;
}
coral::expected<coral::u64, coral::io_error> 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<coral::i64>(offset), RW_SEEK_SET)};
if (byte_position == -1) return coral::io_error::unavailable;
return static_cast<coral::u64>(byte_position);
}
coral::expected<coral::u64, coral::io_error> 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<coral::u64>(byte_position);
}
coral::expected<coral::usize, coral::io_error> write(coral::slice<coral::u8 const> const & data) override {
if (!this->is_open()) return coral::io_error::unavailable;
coral::usize const data_written{SDL_RWwrite(this->rw_ops, data.pointer, sizeof(uint8_t), data.length)};
if ((data_written == 0) && (SDL_GetError() != nullptr)) return coral::io_error::unavailable;
return data_written;
}
private:
SDL_RWops * rw_ops{nullptr};
};
struct sandboxed_fs : public coral::fs {
sandboxed_fs() {
char * const path{SDL_GetBasePath()};
if (path == nullptr) return;
for (coral::usize index = 0; path[index] != 0; index += 1)
this->sandbox_path.put(path[index]);
SDL_free(path);
this->access_rules.can_read = true;
}
sandboxed_fs(coral::path const & organization_name, coral::path const & app_name) {
char * const path{SDL_GetPrefPath(organization_name.begin(), app_name.begin())};
if (path == nullptr) return;
for (coral::usize index = 0; path[index] != 0; index += 1)
this->sandbox_path.put(path[index]);
SDL_free(path);
this->access_rules.can_read = true;
}
access_rules query_access() override {
return this->access_rules;
}
void read_file(coral::path const & file_path, coral::closure<void(coral::file_reader &)> const & then) override {
if (!this->access_rules.can_read) return;
native_path sandbox_file_path;
{
coral::expected const written = sandbox_file_path.write(this->sandbox_path.as_slice());
if (!written.is_ok() || (written.value() != this->sandbox_path.count())) return;
}
{
coral::expected const written =
sandbox_file_path.write(file_path.as_slice().as_bytes());
if (!written.is_ok() || (written.value() != this->sandbox_path.count())) return;
}
native_file file;
if (file.open(sandbox_file_path, native_file::open_mode::read_only) !=
native_file::open_result::ok) return;
then(file);
if (file.close() != native_file::close_result::ok)
// Error orphaned file handle!
return;
}
void write_file(coral::path const & file_path, coral::closure<void(coral::file_writer &)> const & then) override {
if (!this->access_rules.can_write) return;
native_path sandbox_file_path;
{
coral::expected const written = sandbox_file_path.write(this->sandbox_path.as_slice());
if (!written.is_ok() || (written.value() != this->sandbox_path.count())) return;
}
{
coral::expected const written =
sandbox_file_path.write(file_path.as_slice().as_bytes());
if (!written.is_ok() || (written.value() != this->sandbox_path.count())) return;
}
native_file file;
if (file.open(sandbox_file_path, native_file::open_mode::overwrite) !=
native_file::open_result::ok) return;
then(file);
if (file.close() != native_file::close_result::ok)
// Error orphaned file handle!
return;
}
private:
native_path sandbox_path;
access_rules access_rules{
.can_read = false,
.can_write = false,
};
};
export namespace app {
enum class log_level {
notice,
warning,
error,
};
struct client {
coral::fs & base() {
return this->base_sandbox;
}
void display(coral::u16 screen_width, coral::u16 screen_height) {
SDL_SetWindowSize(this->window, screen_width, screen_height);
SDL_ShowWindow(this->window);
}
void log(log_level level, coral::slice<char const> const & message) {
coral::i32 const length{static_cast<coral::i32>(
coral::min(message.length, static_cast<size_t>(coral::i32_max)))};
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION,
SDL_LOG_PRIORITY_INFO, "%.*s", length, message.pointer);
}
bool poll() {
while (SDL_PollEvent(&this->event) != 0) {
switch (this->event.type) {
case SDL_QUIT: return false;
}
}
return true;
}
coral::fs & resources() {
return this->resources_archive;
}
static int run(coral::path const & title, coral::closure<int(client &)> const & start) {
constexpr int windowpos {SDL_WINDOWPOS_UNDEFINED};
constexpr coral::u32 windowflags {SDL_WINDOW_HIDDEN};
constexpr int window_width {640};
constexpr int window_height {480};
SDL_Window * const window {SDL_CreateWindow(
title.begin(), windowpos, windowpos, window_width, window_height, windowflags)};
if (window == nullptr) return 0xff;
struct : public coral::allocator {
coral::u8 * reallocate(coral::u8 * allocation, coral::usize requested_size) override {
return reinterpret_cast<coral::u8 *>(SDL_realloc(allocation, requested_size));
}
void deallocate(void * allocation) override {
SDL_free(allocation);
}
} allocator;
client app_client {&allocator, window, title};
return start(app_client);
}
coral::allocator & thread_safe_allocator() {
return *this->allocator;
}
coral::fs & user() {
return this->user_sandbox;
}
private:
client(coral::allocator * allocator, SDL_Window * window,
coral::path const & title) : user_sandbox{"ona", title} {
this->allocator = allocator;
this->window = window;
}
coral::allocator * allocator;
SDL_Window * window;
SDL_Event event;
sandboxed_fs base_sandbox;
sandboxed_fs user_sandbox;
oar::archive resources_archive{&base_sandbox, "base.oar"};
};
}