ona/source/app.cpp

330 lines
7.7 KiB
C++
Raw Normal View History

2023-02-19 17:45:40 +01:00
module;
#include <SDL2/SDL.h>
2023-02-18 04:34:40 +01:00
export module app;
2023-02-19 17:50:29 +01:00
import coral;
import coral.files;
import coral.image;
import coral.math;
2023-02-18 04:34:40 +01:00
2023-02-19 17:45:40 +01:00
import oar;
2023-02-20 17:12:29 +01:00
namespace app {
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;
}
2023-02-18 04:34:40 +01:00
2023-02-20 17:12:29 +01:00
close_result close() {
if (::SDL_RWclose(this->rw_ops) != 0) return close_result::io_unavailable;
2023-02-18 04:34:40 +01:00
2023-02-20 17:12:29 +01:00
this->rw_ops = nullptr;
2023-02-18 04:34:40 +01:00
2023-02-20 17:12:29 +01:00
return close_result::ok;
}
2023-02-18 04:34:40 +01:00
2023-02-20 17:12:29 +01:00
bool is_open() const {
return this->rw_ops != nullptr;
}
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
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();
}
2023-02-18 04:34:40 +01:00
2023-02-20 17:12:29 +01:00
this->rw_ops = ::SDL_RWFromFile(reinterpret_cast<char const *>(this->path_buffer), "r");
2023-02-18 04:34:40 +01:00
2023-02-20 17:12:29 +01:00
if (this->rw_ops == nullptr) return open_result::not_found;
2023-02-18 04:34:40 +01:00
2023-02-20 17:12:29 +01:00
return open_result::ok;
}
2023-02-18 04:34:40 +01:00
2023-02-20 17:12:29 +01:00
coral::expected<coral::usize, coral::io_error> read(coral::slice<coral::u8> const & buffer) override {
if (!this->is_open()) return coral::io_error::unavailable;
2023-02-18 04:34:40 +01:00
2023-02-20 17:12:29 +01:00
coral::usize const bytes_read{::SDL_RWread(this->rw_ops, buffer.pointer, sizeof(uint8_t), buffer.length)};
2023-02-18 04:34:40 +01:00
2023-02-20 17:12:29 +01:00
if ((bytes_read == 0) && (::SDL_GetError() != nullptr)) return coral::io_error::unavailable;
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
return bytes_read;
}
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
coral::expected<coral::u64, coral::io_error> seek(coral::u64 offset) override {
if (!this->is_open()) return coral::io_error::unavailable;
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
// TODO: Fix cast.
coral::i64 const byte_position{
::SDL_RWseek(this->rw_ops, static_cast<coral::i64>(offset), RW_SEEK_SET)};
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
if (byte_position == -1) return coral::io_error::unavailable;
return static_cast<coral::u64>(byte_position);
}
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
coral::expected<coral::u64, coral::io_error> tell() override {
if (!this->is_open()) return coral::io_error::unavailable;
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
coral::i64 const byte_position{::SDL_RWseek(this->rw_ops, 0, RW_SEEK_SET)};
2023-02-18 04:34:40 +01:00
2023-02-20 17:12:29 +01:00
if (byte_position == -1) return coral::io_error::unavailable;
2023-02-20 17:12:29 +01:00
return static_cast<coral::u64>(byte_position);
}
2023-02-20 17:12:29 +01:00
private:
static constexpr coral::usize path_max{4096};
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
coral::u8 path_buffer[path_max];
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
coral::fs * fs;
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
::SDL_RWops * rw_ops;
};
}
2023-02-19 17:45:40 +01:00
struct base_directory : public coral::fs {
base_directory() : directory_path{} {
char * const path{::SDL_GetBasePath()};
2023-02-19 17:45:40 +01:00
if (path == nullptr) return;
2023-02-19 17:45:40 +01:00
coral::usize path_length{0};
2023-02-19 17:45:40 +01:00
while (path[path_length] != 0) path_length += 1;
2023-02-19 17:45:40 +01:00
if (path_length == 0) {
::SDL_free(path);
return;
2023-02-19 17:45:40 +01:00
}
2023-02-18 04:34:40 +01:00
this->directory_path = {path, path_length};
}
2023-02-18 04:34:40 +01:00
~base_directory() override {
::SDL_free(this->directory_path.begin());
}
2023-02-19 17:45:40 +01:00
void read_file(coral::path const & file_path, coral::callable<void(coral::file_reader &)> const & then) override {
if (this->directory_path.length == 0) return;
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
app::file_reader reader{this};
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
if (reader.open(file_path) != app::file_reader::open_result::ok) return;
2023-02-19 17:45:40 +01:00
then(reader);
2023-02-19 17:45:40 +01:00
2023-02-20 17:12:29 +01:00
if (reader.close() != app::file_reader::close_result::ok) return;
}
2023-02-19 17:45:40 +01:00
void write_file(coral::path const & file_path, coral::callable<void(coral::file_writer &)> const & then) override {
// Directory is read-only.
}
2023-02-19 17:45:40 +01:00
protected:
coral::slice<char> directory_path;
};
2023-02-19 17:45:40 +01:00
struct user_directory : public coral::fs {
user_directory(coral::path const & title) : directory_path{} {
char * const path{::SDL_GetPrefPath("ona", title.begin())};
2023-02-19 17:45:40 +01:00
if (path == nullptr) return;
2023-02-19 17:45:40 +01:00
coral::usize path_length{0};
while (path[path_length] != 0) path_length += 1;
if (path_length == 0) {
::SDL_free(path);
return;
2023-02-19 17:45:40 +01:00
}
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<void(coral::file_reader &)> const & then) override {
if (this->directory_path.length == 0) return;
2023-02-20 17:12:29 +01:00
app::file_reader reader{this};
2023-02-20 17:12:29 +01:00
if (reader.open(file_path) != app::file_reader::open_result::ok) return;
then(reader);
2023-02-20 17:12:29 +01:00
if (reader.close() != app::file_reader::close_result::ok) return;
}
void write_file(coral::path const & file_path, coral::callable<void(coral::file_writer &)> const & then) override {
// Directory is read-only.
}
protected:
coral::slice<char> directory_path;
};
export namespace app {
enum class log_level {
notice,
warning,
error,
};
struct system {
system(coral::path const & title) : res{&base, "base_directory.oar"}, user{title} {}
coral::fs & base_fs() {
return this->base;
2023-02-19 17:45:40 +01:00
}
bool poll() {
while (::SDL_PollEvent(&this->sdl_event) != 0) {
switch (this->sdl_event.type) {
case SDL_QUIT: return false;
}
}
return true;
}
2023-02-19 17:50:29 +01:00
coral::fs & res_fs() {
return this->res;
2023-02-19 17:45:40 +01:00
}
2023-02-19 17:50:29 +01:00
void log(app::log_level level, coral::slice<char const> const & message) {
2023-02-19 18:16:43 +01:00
coral::i32 const length{static_cast<coral::i32>(
coral::min(message.length, static_cast<size_t>(coral::i32_max)))};
2023-02-19 17:45:40 +01:00
::SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION,
SDL_LOG_PRIORITY_INFO, "%.*s", length, message.pointer);
}
2023-02-19 17:50:29 +01:00
coral::allocator & thread_safe_allocator() {
2023-02-19 17:45:40 +01:00
return this->allocator;
}
2023-02-19 17:50:29 +01:00
coral::fs & user_fs() {
return this->user;
2023-02-19 17:45:40 +01:00
}
2023-02-18 04:34:40 +01:00
2023-02-19 17:45:40 +01:00
private:
::SDL_Event sdl_event;
2023-02-18 04:34:40 +01:00
2023-02-19 17:50:29 +01:00
struct : public coral::allocator {
coral::u8 * reallocate(coral::u8 * maybe_allocation, coral::usize requested_size) override {
return reinterpret_cast<coral::u8 *>(::SDL_malloc(requested_size));
2023-02-19 17:45:40 +01:00
}
void deallocate(void * allocation) override {
::SDL_free(allocation);
}
} allocator;
2023-02-18 04:34:40 +01:00
base_directory base;
2023-02-19 17:45:40 +01:00
user_directory user;
2023-02-19 17:45:40 +01:00
oar::archive res;
2023-02-18 04:34:40 +01:00
};
struct graphics {
2023-02-19 17:45:40 +01:00
enum class [[nodiscard]] show_result {
ok,
2023-02-18 04:34:40 +01:00
out_of_memory,
};
struct canvas {
2023-02-19 17:50:29 +01:00
coral::color background_color;
2023-02-18 04:34:40 +01:00
};
2023-02-19 17:50:29 +01:00
graphics(coral::path const & title) {
2023-02-19 17:45:40 +01:00
this->retitle(title);
}
2023-02-18 04:34:40 +01:00
2023-02-19 18:16:43 +01:00
void present() {
if (this->sdl_renderer != nullptr) {
::SDL_RenderPresent(this->sdl_renderer);
}
}
2023-02-19 17:45:40 +01:00
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());
2023-02-18 04:34:40 +01:00
2023-02-19 17:45:40 +01:00
SDL_RenderClear(this->sdl_renderer);
}
}
2023-02-18 04:34:40 +01:00
2023-02-19 17:50:29 +01:00
void retitle(coral::path const & title) {
2023-02-19 17:45:40 +01:00
this->title = title;
if (this->sdl_window != nullptr)
::SDL_SetWindowTitle(this->sdl_window, this->title.begin());
}
2023-02-19 17:50:29 +01:00
show_result show(coral::u16 physical_width, coral::u16 physical_height) {
2023-02-19 17:45:40 +01:00
if (this->sdl_window == nullptr) {
constexpr int sdl_windowpos = SDL_WINDOWPOS_UNDEFINED;
2023-02-19 17:50:29 +01:00
constexpr coral::u32 sdl_windowflags = 0;
2023-02-19 17:45:40 +01:00
this->sdl_window = ::SDL_CreateWindow(this->title.begin(), sdl_windowpos,
sdl_windowpos, static_cast<int>(physical_width), static_cast<int>(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) {
2023-02-19 17:50:29 +01:00
constexpr coral::u32 sdl_rendererflags = 0;
2023-02-19 17:45:40 +01:00
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;
}
private:
2023-02-19 17:50:29 +01:00
coral::path title;
2023-02-19 17:45:40 +01:00
::SDL_Window * sdl_window = nullptr;
::SDL_Renderer * sdl_renderer = nullptr;
2023-02-18 04:34:40 +01:00
};
2023-02-19 17:45:40 +01:00
2023-02-19 17:50:29 +01:00
using graphical_runnable = coral::callable<int(system &, graphics &)>;
2023-02-19 17:45:40 +01:00
2023-02-19 17:50:29 +01:00
int display(coral::path const & title, graphical_runnable const & run) {
2023-02-19 17:45:40 +01:00
system app_system{title};
graphics app_graphics{title};
return run(app_system, app_graphics);
}
2023-02-18 04:34:40 +01:00
}