module; #include export module app; import core; import core.files; import core.image; import core.lalgebra; import oar; export namespace app { struct directory : public core::fs { using core::fs::access_result; struct rules { bool can_read; bool can_write; }; directory() : path_buffer{0} {} access_result read_file(core::path const & file_path, core::callable const & then) { 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; } 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); { core::slice const path_buffer_slice{this->path_buffer}; core::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)); } } access_result write_file(core::path const & file_path, core::callable const & then) { 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 { notice, warning, error, }; struct system { system(core::path const & title) : res_archive{} { constexpr directory::rules read_only_rules = {.can_read = true}; { char * const path = ::SDL_GetBasePath(); if (path == nullptr) { this->cwd_directory.target("./", read_only_rules); } else { core::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) { 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 [[nodiscard]] show_result { ok, out_of_memory, }; struct canvas { core::color background_color; }; graphics(core::path const & title) { this->retitle(title); } 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()); SDL_RenderClear(this->sdl_renderer); } } 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); } }