From d659dcde10ba7c0108aff57e76fbe8ebe92c6051 Mon Sep 17 00:00:00 2001 From: Timerix Date: Sat, 26 Apr 2025 00:59:47 +0500 Subject: [PATCH] refactored MainWindow and Engine, changed project structure --- src/Engine.cpp | 114 +++++++++ src/Engine.hpp | 50 ++++ src/GUI/MainWindow.cpp | 209 +++++------------ src/GUI/MainWindow.hpp | 39 ++-- src/Game/Engine.cpp | 37 --- src/Game/Engine.hpp | 30 --- src/Game/GameObject.cpp | 2 +- src/Game/GameObject.hpp | 8 +- src/Game/GameObjectPool.cpp | 10 +- src/Game/GameObjectPool.hpp | 2 +- src/Mono/Assembly.cpp | 4 +- src/Mono/Mono.cpp | 2 +- src/Mono/Mono.hpp | 10 +- src/Mono/Runtime.cpp | 4 +- src/Resources/Resources.cpp | 16 +- src/Resources/Resources.hpp | 4 +- src/Resources/fonts.cpp | 4 +- src/Resources/fonts.hpp | 4 +- src/Resources/textures.cpp | 12 +- src/Resources/textures.hpp | 10 +- src/{ => common}/UsefulException.cpp | 0 src/{ => common}/UsefulException.hpp | 0 src/common/function_shared_ptr.hpp | 50 ++++ src/{ => common}/math.hpp | 0 src/{format.cpp => common/ougge_format.cpp} | 4 +- src/common/ougge_format.hpp | 6 + src/{ => common}/std.hpp | 0 src/{ => common}/time.cpp | 0 src/{ => common}/time.hpp | 0 src/format.hpp | 6 - src/function_shared_ptr.hpp | 30 --- src/game/GameObject.cpp | 30 +++ src/game/GameObject.hpp | 45 ++++ src/game/GameObjectPool.cpp | 130 +++++++++++ src/game/GameObjectPool.hpp | 70 ++++++ src/gui/ErrorWindow.cpp | 11 + src/gui/MainWindow.cpp | 218 ++++++++++++++++++ src/gui/MainWindow.hpp | 45 ++++ src/gui/SceneView.hpp | 0 src/gui/gui.hpp | 11 + .../exceptions.cpp => gui/gui_exceptions.cpp} | 10 +- .../exceptions.hpp => gui/gui_exceptions.hpp} | 8 +- src/main.cpp | 35 +-- src/mono/Assembly.cpp | 17 ++ src/mono/Runtime.cpp | 30 +++ src/mono/mono.cpp | 81 +++++++ src/mono/mono.hpp | 187 +++++++++++++++ src/resources/embedded_resources.h | 64 +++++ src/resources/fonts.cpp | 33 +++ src/resources/fonts.hpp | 12 + src/resources/resources.cpp | 94 ++++++++ src/resources/resources.hpp | 63 +++++ src/resources/textures.cpp | 96 ++++++++ src/resources/textures.hpp | 44 ++++ 54 files changed, 1662 insertions(+), 339 deletions(-) create mode 100644 src/Engine.cpp create mode 100644 src/Engine.hpp delete mode 100644 src/Game/Engine.cpp delete mode 100644 src/Game/Engine.hpp rename src/{ => common}/UsefulException.cpp (100%) rename src/{ => common}/UsefulException.hpp (100%) create mode 100644 src/common/function_shared_ptr.hpp rename src/{ => common}/math.hpp (100%) rename src/{format.cpp => common/ougge_format.cpp} (94%) create mode 100644 src/common/ougge_format.hpp rename src/{ => common}/std.hpp (100%) rename src/{ => common}/time.cpp (100%) rename src/{ => common}/time.hpp (100%) delete mode 100644 src/format.hpp delete mode 100644 src/function_shared_ptr.hpp create mode 100644 src/game/GameObject.cpp create mode 100644 src/game/GameObject.hpp create mode 100644 src/game/GameObjectPool.cpp create mode 100644 src/game/GameObjectPool.hpp create mode 100644 src/gui/ErrorWindow.cpp create mode 100644 src/gui/MainWindow.cpp create mode 100644 src/gui/MainWindow.hpp create mode 100644 src/gui/SceneView.hpp create mode 100644 src/gui/gui.hpp rename src/{GUI/exceptions.cpp => gui/gui_exceptions.cpp} (75%) rename src/{GUI/exceptions.hpp => gui/gui_exceptions.hpp} (73%) create mode 100644 src/mono/Assembly.cpp create mode 100644 src/mono/Runtime.cpp create mode 100644 src/mono/mono.cpp create mode 100644 src/mono/mono.hpp create mode 100644 src/resources/embedded_resources.h create mode 100644 src/resources/fonts.cpp create mode 100644 src/resources/fonts.hpp create mode 100644 src/resources/resources.cpp create mode 100644 src/resources/resources.hpp create mode 100644 src/resources/textures.cpp create mode 100644 src/resources/textures.hpp diff --git a/src/Engine.cpp b/src/Engine.cpp new file mode 100644 index 0000000..cb648c2 --- /dev/null +++ b/src/Engine.cpp @@ -0,0 +1,114 @@ +#include "Engine.hpp" +#include "gui/gui.hpp" + +namespace ougge { + +Engine::Engine() + : gameObjectPool(GAMEOBJECTPOOL_SIZE) +{ +} + +void Engine::init(){ + engineManagedAssembly = mono.loadAssembly("Ougge.dll"); + gameObjectClass = engineManagedAssembly->getClass("Ougge", "GameObject"); + gameObjectCtor = Mono::Method(gameObjectClass, ".ctor"); + gameObjectInvokeUpdate = Mono::Method(gameObjectClass, "InvokeUpdate"); + gameObjectTryCreateComponent = Mono::Method(gameObjectClass, "TryCreateComponent_internal"); +} + +void Engine::openMainWindow(const std::string& window_title){ + mainWindow.open(window_title); +} + +void Engine::startLoop(){ + if(loop_running) + throw UsefulException("loop is running already"); + + nsec_t prev_update_time_ns = getMonotonicTimeNsec(); + loop_running=true; + // main loop + while(loop_running){ + mainWindow.pollEvents(&loop_running); + if(!loop_running) + break; + + nsec_t update_time_ns = getMonotonicTimeNsec(); + if(update_time_ns < prev_update_time_ns) + throw UsefulException("monotonic clock returned unexpected value"); + f64 delta_time_s = (f64)(update_time_ns - prev_update_time_ns) / 1e9; + prev_update_time_ns = update_time_ns; + + tryDrawFrame(delta_time_s); + + nsec_t after_update_time_ns = getMonotonicTimeNsec(); + nsec_t frame_delay_ns = (nsec_t)1e9 / mainWindow.fps_max - (after_update_time_ns - update_time_ns); + if(frame_delay_ns > 0){ + SDL_Delay(frame_delay_ns / 1e6); + } + } + + mainWindow.close(); +} + +void Engine::stopLoop(){ + loop_running = false; +} + +void Engine::tryDrawFrame(f64 deltaTime){ + static std::string error_message; + static bool draw_error_window = false; + try { + mainWindow.beginFrame(); + + if(draw_error_window) + gui::drawErrorWindow(error_message, &draw_error_window); + else { + updateGameObjects(deltaTime); + if(!updateCallback.isNull()) + updateCallback(deltaTime); + } + mainWindow.endFrame(); + } + catch(const std::exception& e){ + error_message = "Catched exception: " + std::string(e.what()); + draw_error_window = true; + std::cerr<; + +class Engine { + bool loop_running = false; + + game::GameObjectPool gameObjectPool; + u64 obj_id = 0; + MonoClass* gameObjectClass; + Mono::Method gameObjectCtor; + Mono::Method gameObjectInvokeUpdate; + Mono::Method gameObjectTryCreateComponent; + +public: + gui::MainWindow mainWindow; + UpdateFunc_t updateCallback; + + Mono::RuntimeJIT mono; + std::shared_ptr engineManagedAssembly; + + resources::CacheStorage textures; + + Engine(); + void init(); + void openMainWindow(const std::string& window_title); + // start game loop on the current thread + void startLoop(); + void stopLoop(); + + game::GameObject& createGameObject(); + bool tryCreateComponent(game::GameObject& obj, const std::string& componentClassName); + + +private: + void tryDrawFrame(f64 deltaTime); + void updateGameObjects(f64 deltaTime); +}; + +} diff --git a/src/GUI/MainWindow.cpp b/src/GUI/MainWindow.cpp index cd3a544..a895cfb 100644 --- a/src/GUI/MainWindow.cpp +++ b/src/GUI/MainWindow.cpp @@ -2,18 +2,18 @@ #include #include #include "MainWindow.hpp" -#include "exceptions.hpp" -#include "../format.hpp" -#include "../Resources/fonts.hpp" -#include "../Resources/textures.hpp" -#include "../math.hpp" +#include "gui_exceptions.hpp" +#include "../common/ougge_format.hpp" +#include "../resources/fonts.hpp" +#include "../resources/textures.hpp" +#include "../common/math.hpp" -namespace ougge::GUI { +namespace ougge::gui { f32 MainWindow::getDPI(){ - int w=0, h=0; + i32 w=0, h=0; SDL_GetRendererOutputSize(sdl_renderer, &w, &h); - int sim_w=0, sim_h=0; + i32 sim_w=0, sim_h=0; SDL_GetWindowSize(sdl_window, &sim_w, &sim_h); f32 wdpi=(f32)w / sim_w; f32 hdpi=(f32)h / sim_h; @@ -21,13 +21,11 @@ f32 MainWindow::getDPI(){ return dpi; } -void MainWindow::open(const char* window_title, UpdateFunc_t _update){ - update = _update; - +void MainWindow::open(const std::string& window_title){ SDL_TRY(SDL_Init(SDL_INIT_EVERYTHING)); SDL_version v; SDL_GetVersion(&v); - std::cout< textures; - Resources::Texture& tutel = textures.getOrCreate("tutel.png", sdl_renderer); - Resources::SDL_RenderCopyExF_Params rp; - rp.target_section = SDL_FRectConstruct(100, 100, 400, 400); - static int si = 1; - rp.rotation_angle = M_PI_4 * (si++ / fps_max); - tutel.render(rp); - - - ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), sdl_renderer); - // Swap buffers - SDL_RenderPresent(sdl_renderer); -} - -void MainWindow::startUpdateLoop(){ - if(loop_running) - throw UsefulException("loop is running already"); - - nsec_t prev_update_time_ns = getMonotonicTimeNsec(); - loop_running=true; - // main loop - while(loop_running){ - poll_events(false); - - nsec_t update_time_ns = getMonotonicTimeNsec(); - if(update_time_ns < prev_update_time_ns) - throw UsefulException("monotonic clock returned unexpected value"); - f64 delta_time_s = (f64)(update_time_ns - prev_update_time_ns) / 1e9; - prev_update_time_ns = update_time_ns; - - update(delta_time_s); - draw_frame(); - - nsec_t after_update_time_ns = getMonotonicTimeNsec(); - nsec_t frame_delay_ns = (nsec_t)1e9 / fps_max - (after_update_time_ns - update_time_ns); - if(frame_delay_ns > 0){ - SDL_Delay(frame_delay_ns / 1e6); - } - } - - destroy(); -} - -void MainWindow::destroy(){ +void MainWindow::close(){ ImGui_ImplSDLRenderer2_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); @@ -215,8 +85,52 @@ void MainWindow::destroy(){ SDL_Quit(); } -void MainWindow::close(){ - loop_running = false; +void MainWindow::pollEvents(bool* loopRunning){ + SDL_Event event; + while (SDL_PollEvent(&event)) { + ImGui_ImplSDL2_ProcessEvent(&event); + switch(event.type){ + case SDL_QUIT: { + *loopRunning = false; + break; + } + case SDL_WINDOWEVENT: { + if(event.window.event == SDL_WINDOWEVENT_CLOSE + && event.window.windowID == SDL_GetWindowID(sdl_window)) + { + *loopRunning = false; + } + break; + } + } + } +} + + +void MainWindow::beginFrame(){ + // Start the Dear ImGui frame + ImGui_ImplSDLRenderer2_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + ImGuiIO& io = ImGui::GetIO(); + SDL_RenderSetScale(sdl_renderer, io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); + SDL_SetRenderDrawColor(sdl_renderer, + (u8)(clear_color.x * 255), + (u8)(clear_color.y * 255), + (u8)(clear_color.z * 255), + (u8)(clear_color.w * 255)); + SDL_RenderClear(sdl_renderer); + + draw_bg_window(); + draw_debug_window(); +} + +void MainWindow::endFrame(){ + ImGui::Render(); + ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), sdl_renderer); + // Swap buffers + SDL_RenderPresent(sdl_renderer); } void MainWindow::draw_bg_window(){ @@ -285,13 +199,14 @@ void MainWindow::draw_bg_window(){ void MainWindow::draw_debug_window(){ ImGuiIO& io = ImGui::GetIO(); - ImGui::Begin("Debug Options"); + if(ImGui::Begin("Debug Options", &show_debug_window)){ ImGui::ColorEdit3("clear_color", (float*)&clear_color); ImGui::InputInt("fps_max", &fps_max); ImGui::Text("Application average %.3f ms/frame (%.2f FPS)", 1000.0f / io.Framerate, io.Framerate); ImGui::Checkbox("Demo Window", &show_demo_window); ImGui::Checkbox("Metrics/Debug Window", &show_metrics_window); - ImGui::End(); + ImGui::End(); + } if (show_demo_window) ImGui::ShowDemoWindow(&show_demo_window); diff --git a/src/GUI/MainWindow.hpp b/src/GUI/MainWindow.hpp index df9b785..70fad7a 100644 --- a/src/GUI/MainWindow.hpp +++ b/src/GUI/MainWindow.hpp @@ -2,47 +2,42 @@ #include #include -#include "../std.hpp" -#include "../time.hpp" -#include +#include "../common/std.hpp" +#include "../common/time.hpp" /// converts hex color to float vector #define RGBAHexToF(R8,G8,B8,A8) ImVec4(((u8)35)/255.0f, ((u8)35)/255.0f, ((u8)50)/255.0f, ((u8)255)/255.0f) /// converts float vector to hex color #define RGBAFToHex(VEC4) {(u8)(VEC4.x*255), (u8)(VEC4.y*255), (u8)(VEC4.z*255), (u8)(VEC4.w*255)} -namespace ougge::GUI { +namespace ougge::gui { #define default_font "DroidSans" -using UpdateFunc_t = std::function; - class MainWindow { public: - ImVec4 clear_color = RGBAHexToF(35,35,50,255); + i32 fps_max = 60; f32 default_font_size = 14.0f; - int fps_max = 60; - // called on each frame - UpdateFunc_t update = nullptr; - -private: - bool loop_running = false; - bool show_demo_window = false; - bool show_metrics_window = false; + ImVec4 clear_color = RGBAHexToF(35,35,50,255); SDL_Window* sdl_window = nullptr; SDL_Renderer* sdl_renderer = nullptr; +private: + bool show_debug_window = true; + bool show_demo_window = false; + bool show_metrics_window = false; + public: - void open(const char* window_title, UpdateFunc_t update); - void startUpdateLoop(); + void open(const std::string& window_title); void close(); -private: - void destroy(); + /// process io events happened since previous frame + void pollEvents(bool* loopRunning); + void beginFrame(); + void endFrame(); + f32 getDPI(); - void poll_events(bool waitForEvent); - void draw_frame(); - void draw_ui(); +private: void draw_debug_window(); void draw_bg_window(); }; diff --git a/src/Game/Engine.cpp b/src/Game/Engine.cpp deleted file mode 100644 index 148cf56..0000000 --- a/src/Game/Engine.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "Engine.hpp" - -namespace ougge { - -Engine::Engine() - : gameObjectPool(GAMEOBJECTPOOL_SIZE) -{ -} - -void Engine::init(){ - engineManagedAssembly = mono.loadAssembly("Ougge.dll"); - gameObjectClass = engineManagedAssembly->getClass("Ougge", "GameObject"); - gameObjectCtor = Mono::Method(gameObjectClass, ".ctor"); - gameObjectInvokeUpdate = Mono::Method(gameObjectClass, "InvokeUpdate"); - gameObjectTryCreateComponent = Mono::Method(gameObjectClass, "TryCreateComponent_internal"); -} - -void Engine::invokeUpdate(f64 deltaTime){ - for(auto pair : gameObjectPool){ - gameObjectInvokeUpdate(pair.second.getObjectHandle().getObject(), deltaTime); - } -} - -GameObject& Engine::createGameObject(){ - auto pair = gameObjectPool.emplace(GameObject(mono.createObject(gameObjectClass))); - GameObject& obj = pair.second; - gameObjectCtor(obj.getObjectHandle().getObject(), obj_id++, pair.first); - return obj; -} - -bool Engine::tryCreateComponent(GameObject& obj, const std::string& componentClassName){ - Mono::String componentClassNameManaged = mono.createString(componentClassName); - Mono::Bool created = gameObjectTryCreateComponent(obj.getObjectHandle().getObject(), componentClassNameManaged); - return created.wide_bool; -} - -} diff --git a/src/Game/Engine.hpp b/src/Game/Engine.hpp deleted file mode 100644 index 8652e32..0000000 --- a/src/Game/Engine.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "../Mono/Mono.hpp" -#include "GameObjectPool.hpp" - -namespace ougge { - -#define GAMEOBJECTPOOL_SIZE 64*1024 - -class Engine { - GameObjectPool gameObjectPool; - u64 obj_id = 0; - MonoClass* gameObjectClass; - Mono::Method gameObjectCtor; - Mono::Method gameObjectInvokeUpdate; - Mono::Method gameObjectTryCreateComponent; - -public: - Mono::RuntimeJIT mono; - std::shared_ptr engineManagedAssembly; - - Engine(); - void init(); - void invokeUpdate(f64 deltaTime); - - GameObject& createGameObject(); - bool tryCreateComponent(GameObject& obj, const std::string& componentClassName); -}; - -} diff --git a/src/Game/GameObject.cpp b/src/Game/GameObject.cpp index 6aab972..8d9dca4 100644 --- a/src/Game/GameObject.cpp +++ b/src/Game/GameObject.cpp @@ -1,6 +1,6 @@ #include "GameObject.hpp" -namespace ougge { +namespace ougge::game { std::ostream& operator<<(std::ostream& s, Transform& t){ s<<"{ position: {x: "< #include -#include "../math.hpp" -#include "../UsefulException.hpp" -#include "../Mono/Mono.hpp" +#include "../common/math.hpp" +#include "../common/UsefulException.hpp" +#include "../mono/mono.hpp" -namespace ougge { +namespace ougge::game { class GameObject; diff --git a/src/Game/GameObjectPool.cpp b/src/Game/GameObjectPool.cpp index 92f709a..5c25342 100644 --- a/src/Game/GameObjectPool.cpp +++ b/src/Game/GameObjectPool.cpp @@ -2,7 +2,7 @@ #include #include -namespace ougge { +namespace ougge::game { GameObjectPool::GameObjectPool(u32 size) { @@ -75,9 +75,9 @@ u32 GameObjectPool::getNearestUsedIndex(u32 startIndex) GameObject& GameObjectPool::get(u32 index) { if(index >= size) - throw UsefulException(format("index %i is out of size %i", index, size)); + throw UsefulException(ougge_format("index %i is out of size %i", index, size)); if(!isIndexUsed(index)) - throw UsefulException(format("there is no object at index %i", index)); + throw UsefulException(ougge_format("there is no object at index %i", index)); return buffer[index]; } @@ -97,9 +97,9 @@ std::pair GameObjectPool::emplace(GameObject&& new_obj) void GameObjectPool::erase(u32 index) { if(index >= size) - throw UsefulException(format("index %i is out of size %i", index, size)); + throw UsefulException(ougge_format("index %i is out of size %i", index, size)); if(!isIndexUsed(index)) - throw UsefulException(format("there is no object at index %i", index)); + throw UsefulException(ougge_format("there is no object at index %i", index)); buffer[index] = GameObject(); used_indices[index/64] &= ~(u64(1)<<(index%64)); // mark index bit as unused diff --git a/src/Game/GameObjectPool.hpp b/src/Game/GameObjectPool.hpp index 56e777d..8405915 100644 --- a/src/Game/GameObjectPool.hpp +++ b/src/Game/GameObjectPool.hpp @@ -1,6 +1,6 @@ #include "GameObject.hpp" -namespace ougge { +namespace ougge::game { /* Fixed array that stores deleted elements indices as bits in array of u64. diff --git a/src/Mono/Assembly.cpp b/src/Mono/Assembly.cpp index 69232bf..1a40dfa 100644 --- a/src/Mono/Assembly.cpp +++ b/src/Mono/Assembly.cpp @@ -1,4 +1,4 @@ -#include "Mono.hpp" +#include "mono.hpp" namespace Mono { @@ -10,7 +10,7 @@ Assembly::Assembly(MonoAssembly *ptr) MonoClass* Assembly::getClass(const std::string &name_space, const std::string &name){ auto c = mono_class_from_name(image, name_space.c_str(), name.c_str()); if(!c) - throw UsefulException(format("can't get class '%s.%s'", name_space.c_str(), name.c_str())); + throw UsefulException(ougge_format("can't get class '%s.%s'", name_space.c_str(), name.c_str())); return c; } diff --git a/src/Mono/Mono.cpp b/src/Mono/Mono.cpp index 7721a8b..c7fb99a 100644 --- a/src/Mono/Mono.cpp +++ b/src/Mono/Mono.cpp @@ -1,4 +1,4 @@ -#include "Mono.hpp" +#include "mono.hpp" #include namespace Mono { diff --git a/src/Mono/Mono.hpp b/src/Mono/Mono.hpp index a9a0a9f..bffc10f 100644 --- a/src/Mono/Mono.hpp +++ b/src/Mono/Mono.hpp @@ -1,8 +1,8 @@ #pragma once -#include "../std.hpp" -#include "../UsefulException.hpp" -#include "../format.hpp" +#include "../common/std.hpp" +#include "../common/UsefulException.hpp" +#include "../common/ougge_format.hpp" #include #include #include @@ -77,11 +77,12 @@ public: static MonoClass* return_class { getClass() }; method_ptr = tryGetMonoMethod(target_class, name, return_class, arg_classes, sizeof...(ArgTypes)); if(method_ptr == nullptr){ - throw UsefulException(format("can't get method '%s' from class '%s'", + throw UsefulException(ougge_format("can't get method '%s' from class '%s'", name.c_str(), mono_class_get_name(target_class))); } } + // ReturnT not is void template std::enable_if_t::value, RT> operator()(MonoObject* class_instance, ArgTypes... args) const { if(method_ptr == nullptr) @@ -98,6 +99,7 @@ public: return valueFromMonoObject(result); }; + // ReturnT is void template std::enable_if_t::value, RT> operator()(MonoObject* class_instance, ArgTypes... args) const { if(method_ptr == nullptr) diff --git a/src/Mono/Runtime.cpp b/src/Mono/Runtime.cpp index 4a32990..643f5f7 100644 --- a/src/Mono/Runtime.cpp +++ b/src/Mono/Runtime.cpp @@ -1,4 +1,4 @@ -#include "Mono.hpp" +#include "mono.hpp" #include #include #include @@ -23,7 +23,7 @@ RuntimeJIT::~RuntimeJIT(){ std::shared_ptr RuntimeJIT::loadAssembly(const std::string &name){ MonoAssembly* ptr = mono_domain_assembly_open(domain, name.c_str()); if(!ptr) - throw UsefulException(format("can't load assembly '%s'", name.c_str())); + throw UsefulException(ougge_format("can't load assembly '%s'", name.c_str())); return std::make_shared(ptr); } diff --git a/src/Resources/Resources.cpp b/src/Resources/Resources.cpp index 5dd3c15..a996571 100644 --- a/src/Resources/Resources.cpp +++ b/src/Resources/Resources.cpp @@ -1,12 +1,12 @@ #include #include -#include "../UsefulException.hpp" -#include "../format.hpp" -#include "Resources.hpp" +#include "../common/UsefulException.hpp" +#include "../common/ougge_format.hpp" +#include "resources.hpp" #include "embedded_resources.h" -namespace ougge::Resources { +namespace ougge::resources { Resource::Resource(const std::string& path, const std::size_t size, StreamFactoryMethod open_read_steam_func) : path(path), size(size), openStream(open_read_steam_func) @@ -42,7 +42,7 @@ MemoryStreamRead::~MemoryStreamRead(){ static std::unordered_map* _resourceMap = nullptr; -void loadEmbeddedResources(){ +void loadEmbeddedresources(){ for(int i = 0; i < EmbeddedResource_table_count; i++){ const EmbeddedResource& e = EmbeddedResource_table[i]; std::cout <<"loading resource '" << e.path << "' " @@ -55,7 +55,7 @@ void loadEmbeddedResources(){ auto r = _resourceMap->emplace(e.path, Resource(e.path, e.size, embedded_resource_factory)); if(!r.second) - throw UsefulException(format("can't load duplicate resource '%s'", e.path)); + throw UsefulException(ougge_format("can't load duplicate resource '%s'", e.path)); } } @@ -63,13 +63,13 @@ void init(){ if(_resourceMap != nullptr) throw UsefulException("resource has been initialized already"); _resourceMap = new std::unordered_map(); - loadEmbeddedResources(); + loadEmbeddedresources(); } Resource& getResource(const std::string& path){ auto it = _resourceMap->find(path); if(it == _resourceMap->end()) - throw UsefulException(format("can't find resource '%s'", path.c_str())); + throw UsefulException(ougge_format("can't find resource '%s'", path.c_str())); return it->second; } diff --git a/src/Resources/Resources.hpp b/src/Resources/Resources.hpp index b4cd01a..3b266df 100644 --- a/src/Resources/Resources.hpp +++ b/src/Resources/Resources.hpp @@ -1,11 +1,11 @@ #pragma once -#include "../std.hpp" +#include "../common/std.hpp" #include #include #include -namespace ougge::Resources { +namespace ougge::resources { // call this in main() void init(); diff --git a/src/Resources/fonts.cpp b/src/Resources/fonts.cpp index 96e6d63..3ce4b5b 100644 --- a/src/Resources/fonts.cpp +++ b/src/Resources/fonts.cpp @@ -1,9 +1,9 @@ #include -#include "Resources.hpp" +#include "resources.hpp" #include #include -namespace ougge::Resources { +namespace ougge::resources { // select all glyphs from font static const ImWchar glyph_ranges[] = { diff --git a/src/Resources/fonts.hpp b/src/Resources/fonts.hpp index 63aea47..22bf712 100644 --- a/src/Resources/fonts.hpp +++ b/src/Resources/fonts.hpp @@ -1,9 +1,9 @@ #pragma once #include "imgui.h" -#include "../std.hpp" +#include "../common/std.hpp" -namespace ougge::Resources { +namespace ougge::resources { ImFont* ImFont_LoadFromFile(const std::string& file_path, f32 font_size, f32 dpi); diff --git a/src/Resources/textures.cpp b/src/Resources/textures.cpp index 1174fa3..2476a2b 100644 --- a/src/Resources/textures.cpp +++ b/src/Resources/textures.cpp @@ -1,8 +1,8 @@ #include "textures.hpp" #include -#include "../GUI/exceptions.hpp" +#include "../gui/gui_exceptions.hpp" -namespace ougge::Resources { +namespace ougge::resources { Texture::Texture(const Resource& r, SDL_Renderer* renderer) : Texture(*r.openStream(), r.size, renderer) @@ -13,10 +13,10 @@ Texture::Texture(std::istream& s, size_t size, SDL_Renderer* renderer) { SDL_RWops* sdl_stream = SDL_RWFromIStream(s, size); if(!sdl_stream) - throw SDLException(); + throw gui::SDLException(); texture = std::shared_ptr(IMG_LoadTexture_RW(renderer, sdl_stream, 1), SDL_DestroyTexture); if(!texture) - throw IMGException(); + throw gui::IMGException(); SDL_TRY(SDL_QueryTexture(texture.get(), nullptr, nullptr, &w, &h)); } @@ -52,7 +52,7 @@ static Sint64 istream_size(SDL_RWops* context){ return (Sint64)(size_t)context->hidden.unknown.data2; } -static Sint64 istream_seek(SDL_RWops* context, Sint64 offset, int whence){ +static Sint64 istream_seek(SDL_RWops* context, Sint64 offset, i32 whence){ std::istream* stream = (std::istream*)context->hidden.unknown.data1; switch(whence){ case SEEK_SET: stream->seekg(offset, std::ios::beg); break; @@ -72,7 +72,7 @@ static size_t istream_read(SDL_RWops* context, void *ptr, size_t size, size_t ma return stream->bad() ? -1 : stream->gcount() / size; } -static int istream_close(SDL_RWops* context){ +static i32 istream_close(SDL_RWops* context){ if (context) SDL_FreeRW(context); return 0; diff --git a/src/Resources/textures.hpp b/src/Resources/textures.hpp index 8daea4f..2c05b7c 100644 --- a/src/Resources/textures.hpp +++ b/src/Resources/textures.hpp @@ -3,10 +3,10 @@ #include #include #include -#include "Resources.hpp" -#include "../math.hpp" +#include "resources.hpp" +#include "../common/math.hpp" -namespace ougge::Resources { +namespace ougge::resources { #define SDL_RectConstruct(X, Y, W, H) (SDL_Rect){X, Y, W, H} #define SDL_FRectConstruct(X, Y, W, H) (SDL_FRect){X, Y, W, H} @@ -30,8 +30,8 @@ struct SDL_RenderCopyExF_Params { struct Texture { SDL_Renderer* renderer; std::shared_ptr texture; - int w; - int h; + i32 w; + i32 h; Texture(const Resource& r, SDL_Renderer* renderer); Texture(std::istream& s, size_t size, SDL_Renderer* renderer); diff --git a/src/UsefulException.cpp b/src/common/UsefulException.cpp similarity index 100% rename from src/UsefulException.cpp rename to src/common/UsefulException.cpp diff --git a/src/UsefulException.hpp b/src/common/UsefulException.hpp similarity index 100% rename from src/UsefulException.hpp rename to src/common/UsefulException.hpp diff --git a/src/common/function_shared_ptr.hpp b/src/common/function_shared_ptr.hpp new file mode 100644 index 0000000..ff7de84 --- /dev/null +++ b/src/common/function_shared_ptr.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include "UsefulException.hpp" + + +template class function_shared_ptr; +template +class function_shared_ptr +{ +public: + using func_t = std::function; + using ptr_t = std::shared_ptr; + +protected: + ptr_t func_ptr; + +public: + function_shared_ptr() = default; + function_shared_ptr(const function_shared_ptr&) = default; + function_shared_ptr(function_shared_ptr&&) = default; + function_shared_ptr(const ptr_t& p) : func_ptr(p) { } + function_shared_ptr(ptr_t&& p) : func_ptr(p) { } + template + function_shared_ptr(FunctionT f){ func_ptr = std::make_shared(f); } + + function_shared_ptr& operator=(const function_shared_ptr&) = default; + function_shared_ptr& operator=(function_shared_ptr&&) = default; + + bool isNull() const { return func_ptr == nullptr; } + + //TODO: make inline + + // ReturnT is not void + template + std::enable_if_t::value, RT> operator()(ArgTypes... args){ + if(func_ptr == nullptr) + throw UsefulException("function_shared_ptr is null"); + return func_ptr->operator()(args...); + } + + // ReturnT is void + template + std::enable_if_t::value, RT> operator()(ArgTypes... args){ + if(func_ptr == nullptr) + throw UsefulException("function_shared_ptr is null"); + func_ptr->operator()(args...); + } +}; diff --git a/src/math.hpp b/src/common/math.hpp similarity index 100% rename from src/math.hpp rename to src/common/math.hpp diff --git a/src/format.cpp b/src/common/ougge_format.cpp similarity index 94% rename from src/format.cpp rename to src/common/ougge_format.cpp index 8eb6fb4..a8f5a6d 100644 --- a/src/format.cpp +++ b/src/common/ougge_format.cpp @@ -1,9 +1,9 @@ #include #include -#include "format.hpp" +#include "ougge_format.hpp" #include "UsefulException.hpp" -std::string _format(const std::string& format_str, const size_t args_count, ...){ +std::string _ougge_format(const std::string& format_str, const size_t args_count, ...){ va_list vl; va_start(vl, args_count); std::stringstream ss; diff --git a/src/common/ougge_format.hpp b/src/common/ougge_format.hpp new file mode 100644 index 0000000..9eb8203 --- /dev/null +++ b/src/common/ougge_format.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include "std.hpp" + +std::string _ougge_format(const std::string& format_str, const size_t args_count, ...); +#define ougge_format(FORMAT_STR, ARGS...) _ougge_format(FORMAT_STR, count_args(ARGS) ,##ARGS) diff --git a/src/std.hpp b/src/common/std.hpp similarity index 100% rename from src/std.hpp rename to src/common/std.hpp diff --git a/src/time.cpp b/src/common/time.cpp similarity index 100% rename from src/time.cpp rename to src/common/time.cpp diff --git a/src/time.hpp b/src/common/time.hpp similarity index 100% rename from src/time.hpp rename to src/common/time.hpp diff --git a/src/format.hpp b/src/format.hpp deleted file mode 100644 index 9c9485b..0000000 --- a/src/format.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "std.hpp" - -std::string _format(const std::string& format_str, const size_t args_count, ...); -#define format(FORMAT_STR, ARGS...) _format(FORMAT_STR, count_args(ARGS) ,##ARGS) diff --git a/src/function_shared_ptr.hpp b/src/function_shared_ptr.hpp deleted file mode 100644 index bd7a575..0000000 --- a/src/function_shared_ptr.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include -#include - -template -class function_shared_ptr; - -template -class function_shared_ptr { -public: - using func_t = std::function; - using ptr_t = std::shared_ptr; -protected: - ptr_t func_ptr; - -public: - function_shared_ptr() = default; - function_shared_ptr(function_shared_ptr&) = default; - function_shared_ptr(function_shared_ptr&&) = default; - function_shared_ptr(ptr_t& p) { func_ptr = p; } - function_shared_ptr(ptr_t&& p) { func_ptr = p; } - template - function_shared_ptr(FunctionT f){ func_ptr = std::make_shared(f); } - - ReturnT operator()(ArgTypes... args){ return func_ptr->operator()(args...); } - - function_shared_ptr& operator=(function_shared_ptr&) = default; - function_shared_ptr& operator=(function_shared_ptr&&) = default; -}; diff --git a/src/game/GameObject.cpp b/src/game/GameObject.cpp new file mode 100644 index 0000000..8d9dca4 --- /dev/null +++ b/src/game/GameObject.cpp @@ -0,0 +1,30 @@ +#include "GameObject.hpp" + +namespace ougge::game { + +std::ostream& operator<<(std::ostream& s, Transform& t){ + s<<"{ position: {x: "< +#include +#include "../common/math.hpp" +#include "../common/UsefulException.hpp" +#include "../mono/mono.hpp" + +namespace ougge::game { + +class GameObject; + +struct Transform { + Vec2 scale = { 1, 1 }; + Vec2 position = { 0, 0 }; + angle_t rotation = 0; +}; + +std::ostream& operator<<(std::ostream& s, Transform& t); + + +class GameObject { + Mono::ObjectHandle object_handle; + GameObject* parent; + Transform transform; + +public: + /// @warning Do not use this to create objects. + /// This constructor creates null values for GameObject arrays + /// GameObject* array = new GameObject[10]; + /// array[0] = GameObject(initialized_mono_object_ptr) + GameObject() = default; + GameObject(Mono::Object managed_obj); + GameObject(const GameObject& o) = delete; + GameObject(GameObject&& o); + + GameObject& operator=(GameObject&& o); + + inline Mono::ObjectHandle& getObjectHandle() { return object_handle; } + inline GameObject* getParent() { return parent; } + inline void setParent(GameObject* p) { parent = p; } + inline Transform& getTransform() { return transform; } +}; + +} diff --git a/src/game/GameObjectPool.cpp b/src/game/GameObjectPool.cpp new file mode 100644 index 0000000..5c25342 --- /dev/null +++ b/src/game/GameObjectPool.cpp @@ -0,0 +1,130 @@ +#include "GameObjectPool.hpp" +#include +#include + +namespace ougge::game { + +GameObjectPool::GameObjectPool(u32 size) +{ + useful_assert(size % 64 == 0, "size of GameObjectPool must be a multiple of 64"); + this->size = size; + first_unused_index = 0; + buffer = new GameObject[size]; + used_indices = new u64[size/64]; + // std::memset(buffer, 0, size*sizeof(GameObject)); + std::memset(used_indices, 0, size/8); +} + +GameObjectPool::~GameObjectPool() +{ + delete[] buffer; + delete[] used_indices; +} + +bool GameObjectPool::isIndexUsed(u32 index) +{ + return ( used_indices[index/64] & (u64(1)<<(index%64)) ) != 0; +} + +u32 GameObjectPool::getNearestUnusedIndex(u32 startIndex) +{ + if(startIndex >= size) + return -1; + if(!isIndexUsed(startIndex)) + return startIndex; + + u32 i = startIndex/64; + // mark previous bits as used + u64 u = used_indices[i] | ( (u64(1)<= size) + return -1; + if(isIndexUsed(startIndex)) + return startIndex; + + u32 i = startIndex/64; + // mark previous bits as unused + u64 u = used_indices[i] & !( (u64(1)<= size) + throw UsefulException(ougge_format("index %i is out of size %i", index, size)); + if(!isIndexUsed(index)) + throw UsefulException(ougge_format("there is no object at index %i", index)); + return buffer[index]; +} + +std::pair GameObjectPool::emplace(GameObject&& new_obj) +{ + u32 i = first_unused_index; + if(i == u32(-1)) + throw UsefulException("can't put new GameObject to GameObjectPool because it's full"); + + buffer[i] = std::move(new_obj); + GameObject& r = buffer[i]; + used_indices[i/64] |= u64(1)<<(i%64); // mark index bit as used + first_unused_index = getNearestUnusedIndex(i+1); + return std::pair(i, r); +} + +void GameObjectPool::erase(u32 index) +{ + if(index >= size) + throw UsefulException(ougge_format("index %i is out of size %i", index, size)); + if(!isIndexUsed(index)) + throw UsefulException(ougge_format("there is no object at index %i", index)); + + buffer[index] = GameObject(); + used_indices[index/64] &= ~(u64(1)<<(index%64)); // mark index bit as unused + if(index < first_unused_index) + first_unused_index = index; +} + +GameObjectPool::iterator::iterator(GameObjectPool* pool, u32 index) + : pool(pool), index(index) +{ +} + +std::pair GameObjectPool::iterator::operator*() +{ + if(index >= pool->size) + throw UsefulException("can't get value of end() iterator"); + + GameObject& r = pool->buffer[index]; + return std::pair(index, r); +} + +GameObjectPool::iterator& GameObjectPool::iterator::operator++() +{ + index = pool->getNearestUsedIndex(index+1); + return *this; +} + +} diff --git a/src/game/GameObjectPool.hpp b/src/game/GameObjectPool.hpp new file mode 100644 index 0000000..8405915 --- /dev/null +++ b/src/game/GameObjectPool.hpp @@ -0,0 +1,70 @@ +#include "GameObject.hpp" + +namespace ougge::game { + +/* +Fixed array that stores deleted elements indices as bits in array of u64. +Fast emplace, erase and lookup. + +------------------------[construct]------------------------ +operation 'GameObjectPool::GameObjectPool()' took 1.0549 ms +operation 'other_collections_construct' took 0.0133 ms +-------------------------[emplace]------------------------- +operation 'GameObjectPool::emplace' took 8.0557 ms +operation 'vector::emplace_back' took 11.3735 ms +operation 'set::emplace' took 80.5633 ms +operation 'list::emplace_front' took 18.1442 ms +operation 'forward_list::emplace_front' took 11.5467 ms +--------------------------[erase]-------------------------- +operation 'GameObjectPool::erase' took 0.2745 ms +operation 'vector::erase' took 15790.6 ms +operation 'set::erase' took 1.2697 ms +operation 'list::erase_after' took 0.93 ms +operation 'forward_list::erase_after' took 1.1127 ms +-------------------------[iterate]------------------------- +operation 'GameObjectPool::iterate' took 1.1166 ms +operation 'vector::iterate' took 0.8883 ms +operation 'set::iterate' took 2.8011 ms +operation 'list::iterate' took 2.0766 ms +operation 'forward_list::iterate' took 2.0823 ms +*/ + +class GameObjectPool { + GameObject* buffer; + u64* used_indices; + u32 size; + u32 first_unused_index; + + bool isIndexUsed(u32 index); + u32 getNearestUnusedIndex(u32 startIndex); + u32 getNearestUsedIndex(u32 startIndex); +public: + + ///@param size must be a multiple of 64 + GameObjectPool(u32 size); + ~GameObjectPool(); + GameObject& get(u32 index); + std::pair emplace(GameObject&& new_obj); + void erase(u32 index); + + #pragma region iterator class + class iterator { + GameObjectPool* pool; + u32 index = 0; + + public: + iterator(GameObjectPool* pool, u32 index); + std::pair operator*(); + iterator& operator++(); + inline bool operator!=(const iterator& o) const { return index != o.index; }; + inline bool operator==(const iterator& o) const { return index == o.index; }; + }; + #pragma endregion + + inline iterator begin() { return iterator(this, 0); } + inline iterator end() { return iterator(this, -1); } + + friend class iterator; +}; + +} diff --git a/src/gui/ErrorWindow.cpp b/src/gui/ErrorWindow.cpp new file mode 100644 index 0000000..597bbc8 --- /dev/null +++ b/src/gui/ErrorWindow.cpp @@ -0,0 +1,11 @@ +#include "gui.hpp" + +namespace ougge::gui { + +void drawErrorWindow(const std::string& msg, bool* draw_error_window){ + ImGui::Begin("ERROR", draw_error_window); + ImGui::Text("%s", msg.c_str()); + ImGui::End(); +} + +} diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp new file mode 100644 index 0000000..a895cfb --- /dev/null +++ b/src/gui/MainWindow.cpp @@ -0,0 +1,218 @@ +#include +#include +#include +#include "MainWindow.hpp" +#include "gui_exceptions.hpp" +#include "../common/ougge_format.hpp" +#include "../resources/fonts.hpp" +#include "../resources/textures.hpp" +#include "../common/math.hpp" + +namespace ougge::gui { + +f32 MainWindow::getDPI(){ + i32 w=0, h=0; + SDL_GetRendererOutputSize(sdl_renderer, &w, &h); + i32 sim_w=0, sim_h=0; + SDL_GetWindowSize(sdl_window, &sim_w, &sim_h); + f32 wdpi=(f32)w / sim_w; + f32 hdpi=(f32)h / sim_h; + f32 dpi=SDL_sqrtf(wdpi*wdpi + hdpi*hdpi); + return dpi; +} + +void MainWindow::open(const std::string& window_title){ + SDL_TRY(SDL_Init(SDL_INIT_EVERYTHING)); + SDL_version v; + SDL_GetVersion(&v); + std::cout<Pos, ImGuiCond_Always); + ImGui::SetNextWindowSize(viewport->Size, ImGuiCond_Always); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("bg_window", nullptr, window_flags); + ImGui::PopStyleVar(3); + + // DockSpace + ImGuiID dockspace_id = ImGui::GetID("bg_dockspace"); + ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); + + // MenuBar + if(ImGui::BeginMainMenuBar()){ + if(ImGui::BeginMenu("test")){ + if(ImGui::MenuItem("throw exception")){ + ImGui::EndMenu(); + ImGui::EndMainMenuBar(); + ImGui::End(); + throw UsefulException("example exception"); + } + if(ImGui::MenuItem("throw const char*")){ + ImGui::EndMenu(); + ImGui::EndMainMenuBar(); + ImGui::End(); + throw "cptr"; + } + if(ImGui::MenuItem("throw std::string")){ + ImGui::EndMenu(); + ImGui::EndMainMenuBar(); + ImGui::End(); + throw std::string("str"); + } + if(ImGui::MenuItem("throw unknown")){ + ImGui::EndMenu(); + ImGui::EndMainMenuBar(); + ImGui::End(); + throw 111; + } + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } + + ImGui::End(); +} + +void MainWindow::draw_debug_window(){ + ImGuiIO& io = ImGui::GetIO(); + if(ImGui::Begin("Debug Options", &show_debug_window)){ + ImGui::ColorEdit3("clear_color", (float*)&clear_color); + ImGui::InputInt("fps_max", &fps_max); + ImGui::Text("Application average %.3f ms/frame (%.2f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::Checkbox("Demo Window", &show_demo_window); + ImGui::Checkbox("Metrics/Debug Window", &show_metrics_window); + ImGui::End(); + } + + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + if (show_metrics_window) + ImGui::ShowMetricsWindow(&show_metrics_window); +} + +} \ No newline at end of file diff --git a/src/gui/MainWindow.hpp b/src/gui/MainWindow.hpp new file mode 100644 index 0000000..70fad7a --- /dev/null +++ b/src/gui/MainWindow.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include "../common/std.hpp" +#include "../common/time.hpp" + +/// converts hex color to float vector +#define RGBAHexToF(R8,G8,B8,A8) ImVec4(((u8)35)/255.0f, ((u8)35)/255.0f, ((u8)50)/255.0f, ((u8)255)/255.0f) +/// converts float vector to hex color +#define RGBAFToHex(VEC4) {(u8)(VEC4.x*255), (u8)(VEC4.y*255), (u8)(VEC4.z*255), (u8)(VEC4.w*255)} + +namespace ougge::gui { + +#define default_font "DroidSans" + +class MainWindow { +public: + i32 fps_max = 60; + f32 default_font_size = 14.0f; + ImVec4 clear_color = RGBAHexToF(35,35,50,255); + SDL_Window* sdl_window = nullptr; + SDL_Renderer* sdl_renderer = nullptr; + +private: + bool show_debug_window = true; + bool show_demo_window = false; + bool show_metrics_window = false; + +public: + void open(const std::string& window_title); + void close(); + + /// process io events happened since previous frame + void pollEvents(bool* loopRunning); + void beginFrame(); + void endFrame(); + + f32 getDPI(); +private: + void draw_debug_window(); + void draw_bg_window(); +}; + +} diff --git a/src/gui/SceneView.hpp b/src/gui/SceneView.hpp new file mode 100644 index 0000000..e69de29 diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp new file mode 100644 index 0000000..d626910 --- /dev/null +++ b/src/gui/gui.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include "gui_exceptions.hpp" + +namespace ougge::gui { + +void drawErrorWindow(const std::string& msg, bool* draw_error_window); + +} diff --git a/src/GUI/exceptions.cpp b/src/gui/gui_exceptions.cpp similarity index 75% rename from src/GUI/exceptions.cpp rename to src/gui/gui_exceptions.cpp index 3b8b3bf..2073b1e 100644 --- a/src/GUI/exceptions.cpp +++ b/src/gui/gui_exceptions.cpp @@ -1,8 +1,8 @@ -#include "exceptions.hpp" +#include "gui_exceptions.hpp" #include #include -namespace ougge { +namespace ougge::gui { SDLException_::SDLException_(const std::string& _file,const std::string& _func, int _line_n) : UsefulException_(std::string("SDLException: ") + SDL_GetError(), _file, _func, _line_n) @@ -10,10 +10,14 @@ SDLException_::SDLException_(const std::string& _file,const std::string& _func, SDL_ClearError(); } +#ifndef IMG_ClearError + #define IMG_ClearError SDL_ClearError +#endif + IMGException_::IMGException_(const std::string& _file,const std::string& _func, int _line_n) : UsefulException_(std::string("IMGException: ") + IMG_GetError(), _file, _func, _line_n) { - SDL_ClearError(); + IMG_ClearError(); } } diff --git a/src/GUI/exceptions.hpp b/src/gui/gui_exceptions.hpp similarity index 73% rename from src/GUI/exceptions.hpp rename to src/gui/gui_exceptions.hpp index ef86f66..90845ee 100644 --- a/src/GUI/exceptions.hpp +++ b/src/gui/gui_exceptions.hpp @@ -1,8 +1,10 @@ #pragma once -#include "../UsefulException.hpp" -namespace ougge { +#include "../common/std.hpp" +#include "../common/UsefulException.hpp" + +namespace ougge::gui { #define SDLException() SDLException_(__FILE__, __func__, __LINE__) @@ -20,6 +22,6 @@ public: }; -#define SDL_TRY(EXPR) if(EXPR) throw SDLException(); +#define SDL_TRY(EXPR) if(EXPR) throw ::ougge::gui::SDLException(); } diff --git a/src/main.cpp b/src/main.cpp index 6347c80..8d4c823 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,25 @@ #define SDL_MAIN_HANDLED #include #include -#include "GUI/MainWindow.hpp" -#include "Resources/Resources.hpp" -#include "Game/Engine.hpp" -#include "format.hpp" -#include "UsefulException.hpp" +#include "common/UsefulException.hpp" +#include "common/ougge_format.hpp" +#include "Engine.hpp" +#include "gui/gui.hpp" using namespace ougge; +void drawTutel(Engine& engine){ + resources::Texture& tutel = engine.textures.getOrCreate("tutel.png", engine.mainWindow.sdl_renderer); + resources::SDL_RenderCopyExF_Params params; + params.target_section = SDL_FRectConstruct(100, 100, 400, 400); + static i32 si = 1; + params.rotation_angle = M_PI_4 * (si++ / engine.mainWindow.fps_max); + tutel.render(params); +} + int main(int argc, const char** argv){ try { - Resources::init(); + resources::init(); std::cout<<"initialized resource loader"< void { - engine.invokeUpdate(deltaTime); + engine.updateCallback = [&engine](f64 deltaTime) -> void { + drawTutel(engine); }; - - GUI::MainWindow w; - w.open("ougge", updateCallback); + + engine.openMainWindow("ougge"); std::cout<<"created sdl window"< +#include +#include + +namespace Mono { + +RuntimeJIT::RuntimeJIT(const std::string& domain_name){ + mono_set_dirs("mono-libs", "mono-libs"); + mono_set_assemblies_path("mono-libs"); + mono_config_parse("mono-libs/config.xml"); + + domain = mono_jit_init(domain_name.c_str()); + if(!domain) + throw UsefulException("can't initialize mono domain"); +} + +RuntimeJIT::~RuntimeJIT(){ + // TODO: fix segfault on cleanup + // mono_jit_cleanup(domain); +} + +std::shared_ptr RuntimeJIT::loadAssembly(const std::string &name){ + MonoAssembly* ptr = mono_domain_assembly_open(domain, name.c_str()); + if(!ptr) + throw UsefulException(ougge_format("can't load assembly '%s'", name.c_str())); + return std::make_shared(ptr); +} + +} diff --git a/src/mono/mono.cpp b/src/mono/mono.cpp new file mode 100644 index 0000000..c7fb99a --- /dev/null +++ b/src/mono/mono.cpp @@ -0,0 +1,81 @@ +#include "mono.hpp" +#include + +namespace Mono { + +template <> MonoClass* getClass(){ return mono_get_sbyte_class(); } +template <> MonoClass* getClass(){ return mono_get_byte_class(); } +template <> MonoClass* getClass(){ return mono_get_int16_class(); } +template <> MonoClass* getClass(){ return mono_get_uint16_class(); } +template <> MonoClass* getClass(){ return mono_get_int32_class(); } +template <> MonoClass* getClass(){ return mono_get_uint32_class(); } +template <> MonoClass* getClass(){ return mono_get_int64_class(); } +template <> MonoClass* getClass(){ return mono_get_uint64_class(); } +template <> MonoClass* getClass(){ return mono_get_single_class(); } +template <> MonoClass* getClass(){ return mono_get_double_class(); } +template <> MonoClass* getClass(){ return mono_get_boolean_class(); } +template <> MonoClass* getClass(){ return mono_get_char_class(); } +template <> MonoClass* getClass(){ return mono_get_string_class(); } +template <> MonoClass* getClass(){ return mono_get_object_class(); } +template <> MonoClass* getClass(){ return mono_get_void_class(); } + + +void getMethodSignatureTypes(MonoMethod* mono_method, + MonoType** return_type, + std::vector& argument_types) +{ + auto signature = mono_method_signature(mono_method); + void* iter = nullptr; + *return_type = mono_signature_get_return_type(signature); + MonoType* pt = nullptr; + while( (pt = mono_signature_get_params(signature, &iter)) ){ + argument_types.push_back(pt); + } +} + +MonoMethod* tryGetMonoMethod(MonoClass* target_class, const std::string& name, + MonoClass* return_class, MonoClass* arg_classes[], size_t arg_classes_size) +{ + if(target_class == nullptr) + throw UsefulException("target_class is nullptr"); + + // iterate each method + void* iter = nullptr; + MonoMethod* m = nullptr; + std::vector argument_types; + while( (m = mono_class_get_methods(target_class, &iter)) ){ + // compare name + std::string m_name = mono_method_get_name(m); + if(m_name != name) + continue; + + argument_types.clear(); + MonoType* return_type = nullptr; + getMethodSignatureTypes(m, &return_type, argument_types); + // compare argument count + if(argument_types.size() != arg_classes_size) + continue; + + // compare return type + if(!mono_metadata_type_equal(return_type, mono_class_get_type(return_class))) + continue; + + // compare argument types + bool argument_types_mismatch = false; + for(size_t i = 0; i < arg_classes_size; i++){ + if(!mono_metadata_type_equal(argument_types[i], mono_class_get_type(arg_classes[i]))){ + argument_types_mismatch = true; + break; + } + } + if(argument_types_mismatch) + continue; + + // passed all tests successfully + break; + } + + return m; +} + +} diff --git a/src/mono/mono.hpp b/src/mono/mono.hpp new file mode 100644 index 0000000..bffc10f --- /dev/null +++ b/src/mono/mono.hpp @@ -0,0 +1,187 @@ +#pragma once + +#include "../common/std.hpp" +#include "../common/UsefulException.hpp" +#include "../common/ougge_format.hpp" +#include +#include +#include +#include +#include + +namespace Mono { + +typedef i8 SByte; +typedef u8 Byte; +typedef i16 Short; +typedef u16 UShort; +typedef i32 Int; +typedef u32 UInt; +typedef i64 Long; +typedef u64 ULong; +typedef f32 Float; +typedef f64 Double; +typedef union { mono_bool wide_bool; } Bool; //USAGE: Bool t = {true}; +typedef char16_t Char; +typedef MonoString* String; +/// @warning MonoObject can be moved in memory by GC in any time and raw pointer will be invalid. +/// Use ObjectHandle where it is possible. +typedef MonoObject* Object; +typedef void Void; + + +template +MonoClass* getClass(); + +template +void* valueToVoidPtr(T& v){ return &v; } +template<> +inline void* valueToVoidPtr(Object& v){ return v; } +template<> +inline void* valueToVoidPtr(String& v){ return v; } + +template +T valueFromMonoObject(MonoObject* o){ + void* result_value_ptr = mono_object_unbox(o); + if(result_value_ptr == nullptr) + throw UsefulException("can't unbox method value"); + return *((T*)result_value_ptr); +} +template<> +inline Object valueFromMonoObject(MonoObject* o){ return o; } +template<> +inline String valueFromMonoObject(MonoObject* o){ return (String)((void*)o); } + +void getMethodSignatureTypes(MonoMethod* mono_method, + MonoType** return_type, + std::vector& argument_types); + +/// searches for method `name` in `target_class` with return type `return_class` and argument types `arg_classes` +/// @return found method or nullptr +MonoMethod* tryGetMonoMethod(MonoClass* target_class, const std::string& name, + MonoClass* return_class, MonoClass* arg_classes[], size_t arg_classes_size); + + +template class Method; +template +class Method +{ + MonoMethod* method_ptr; + +public: + Method() { method_ptr = nullptr; } + + /// all types must implement getClass() + Method(MonoClass* target_class, const std::string& name){ + static MonoClass* arg_classes[] { getClass()... }; + static MonoClass* return_class { getClass() }; + method_ptr = tryGetMonoMethod(target_class, name, return_class, arg_classes, sizeof...(ArgTypes)); + if(method_ptr == nullptr){ + throw UsefulException(ougge_format("can't get method '%s' from class '%s'", + name.c_str(), mono_class_get_name(target_class))); + } + } + + // ReturnT not is void + template + std::enable_if_t::value, RT> operator()(MonoObject* class_instance, ArgTypes... args) const { + if(method_ptr == nullptr) + throw UsefulException("method_ptr is null"); + + void* arg_array[] = { valueToVoidPtr(args)..., nullptr }; + MonoObject* ex = nullptr; + MonoObject* result = mono_runtime_invoke(method_ptr, class_instance, arg_array, &ex); + if(ex){ + //TODO: call mono_trace_set_printerr_handler from mono/mono/utils/mono-logger.h + mono_print_unhandled_exception(ex); + throw UsefulException("Some C# exception occured"); + } + return valueFromMonoObject(result); + }; + + // ReturnT is void + template + std::enable_if_t::value, RT> operator()(MonoObject* class_instance, ArgTypes... args) const { + if(method_ptr == nullptr) + throw UsefulException("method_ptr is null"); + + void* arg_array[] = { valueToVoidPtr(args)..., nullptr }; + MonoObject* ex = nullptr; + mono_runtime_invoke(method_ptr, class_instance, arg_array, &ex); + if(ex){ + //TODO: call mono_trace_set_printerr_handler from mono/mono/utils/mono-logger.h + mono_print_unhandled_exception(ex); + throw UsefulException("Some C# exception occured"); + } + }; +}; + + +class Assembly { + MonoAssembly* ptr; + MonoImage* image; + +public: + Assembly(MonoAssembly* ptr); + Assembly(const Assembly&) = delete; + + MonoClass* getClass(const std::string& name_space, const std::string& name); + MonoAssembly* getMonoAssembly() const { return ptr; } + MonoImage* getMonoImage() const { return image; } +}; + + +///LINUX: `config.xml`, `mscorelib.dll`, `libmono-native.so` in `mono-libs` directory +/// +///WINDOWS: `config.xml`, `mscorelib.dll` in `mono-libs` directory +class RuntimeJIT { + MonoDomain* domain; +public: + RuntimeJIT(const std::string& domain_name = "OuggeDomain"); + RuntimeJIT(const RuntimeJIT&) = delete; + ~RuntimeJIT(); + + inline MonoDomain* getDomain() { return domain; } + std::shared_ptr loadAssembly(const std::string& name); + + inline Object createObject(MonoClass* klass) { return mono_object_new(domain, klass); } + inline String createString(const std::string& v) { return mono_string_new_len(domain, v.c_str(), v.size()); } + inline String createString(const char* v) { return mono_string_new(domain, v); } +}; + +/// @brief ObjectHandle can be used to store reliable reference to MonoObject. +/// MonoObject can be moved in memory by GC in any time and raw pointer will be invalid. +struct ObjectHandle { + u32 gc_handle; + + inline ObjectHandle() : gc_handle(0) {} + + inline ObjectHandle(Object obj) { + gc_handle = mono_gchandle_new(obj, false); + } + + /// implicitly create new ObjectHandle instead + inline ObjectHandle(const ObjectHandle& o) = delete; + + inline ObjectHandle(ObjectHandle&& o) { + gc_handle = o.gc_handle; + o.gc_handle = 0; + } + + inline ObjectHandle& operator=(ObjectHandle&& o) { + gc_handle = o.gc_handle; + o.gc_handle = 0; + return *this; + } + + inline ~ObjectHandle() { + if(gc_handle) + mono_gchandle_free(gc_handle); + } + + inline Object getObject() const { + return mono_gchandle_get_target(gc_handle); + } +}; + +} diff --git a/src/resources/embedded_resources.h b/src/resources/embedded_resources.h new file mode 100644 index 0000000..96333b1 --- /dev/null +++ b/src/resources/embedded_resources.h @@ -0,0 +1,64 @@ +//////////////////////////////////////////////////////////////// +// This file was generated by resource_embedder // +// https://timerix.ddns.net:3322/Timerix/resource_embedder // +//////////////////////////////////////////////////////////////// +// USAGE: // +// Put it in a SOURCE file to define variables // +// #define EMBEDDED_RESOURCE_DEFINITION // +// #define EMBEDDED_RESOURCE_POSTFIX your_postfix // +// #include "../../src/Resources/embedded_resources.h" // +// // +// Put it in a HEADER file to declare external variables // +// #define EMBEDDED_RESOURCE_POSTFIX your_postfix // +// #include "../../src/Resources/embedded_resources.h" // +// // +// Then you can access embedded files through // +// EmbeddedResource_table_your_postfix. You can get table // +// content by index and put it into a hashtable or a map. // +//////////////////////////////////////////////////////////////// + +#pragma once +#if __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct { + const char* path; + const char* data; + size_t size; +} EmbeddedResource; + +#define RSCAT(A,B,C...) A##B##C +#ifdef EMBEDDED_RESOURCE_POSTFIX + #define _EmbeddedResource_table(P) \ + RSCAT(EmbeddedResource_table_, P) + #define _EmbeddedResource_table_count(P) \ + RSCAT(EmbeddedResource_table_, P, _count) +#else + #define _EmbeddedResource_table(P) \ + EmbeddedResource_table + #define _EmbeddedResource_table_count(P) \ + EmbeddedResource_table_count +#endif +extern const EmbeddedResource _EmbeddedResource_table(EMBEDDED_RESOURCE_POSTFIX)[]; +extern const int _EmbeddedResource_table_count(EMBEDDED_RESOURCE_POSTFIX); + +#ifdef EMBEDDED_RESOURCE_DEFINITION +const EmbeddedResource _EmbeddedResource_table(EMBEDDED_RESOURCE_POSTFIX)[]={ + +}; + +const int _EmbeddedResource_table_count(EMBEDDED_RESOURCE_POSTFIX)=0; +#endif // EMBEDDED_RESOURCE_DEFINITION + +#undef _EmbeddedResource_table +#undef _EmbeddedResource_table_count +#undef EMBEDDED_RESOURCE_POSTFIX +#undef RSCAT + +#if __cplusplus +} +#endif diff --git a/src/resources/fonts.cpp b/src/resources/fonts.cpp new file mode 100644 index 0000000..3ce4b5b --- /dev/null +++ b/src/resources/fonts.cpp @@ -0,0 +1,33 @@ +#include +#include "resources.hpp" +#include +#include + +namespace ougge::resources { + +// select all glyphs from font +static const ImWchar glyph_ranges[] = { + 0x0020, 0xFFFF, 0 +}; + +ImFont* ImFont_LoadFromFile(const std::string& file_path, f32 font_size, f32 dpi){ + ImGuiIO& io = ImGui::GetIO(); + font_size *= dpi; + return io.Fonts->AddFontFromFileTTF(file_path.c_str(), font_size, nullptr, glyph_ranges); +} + +ImFont* ImFont_LoadFromResource(const std::string& font_name, f32 font_size, f32 dpi){ + ImGuiIO& io = ImGui::GetIO(); + font_size *= dpi; + ImFontConfig font_cfg = ImFontConfig(); + std::sprintf(font_cfg.Name, "%s %ipx", font_name.c_str(), (i32)font_size); + + auto& res = getResource("fonts/" + font_name + ".ttf"); + char* font_data = new char[res.size]; + res.openStream()->read(font_data, res.size); + + return io.Fonts->AddFontFromMemoryTTF((void*)(font_data), res.size, + font_size, &font_cfg, glyph_ranges); +} + +} diff --git a/src/resources/fonts.hpp b/src/resources/fonts.hpp new file mode 100644 index 0000000..22bf712 --- /dev/null +++ b/src/resources/fonts.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "imgui.h" +#include "../common/std.hpp" + +namespace ougge::resources { + +ImFont* ImFont_LoadFromFile(const std::string& file_path, f32 font_size, f32 dpi); + +ImFont* ImFont_LoadFromResource(const std::string& font_name, f32 font_size, f32 dpi); + +} diff --git a/src/resources/resources.cpp b/src/resources/resources.cpp new file mode 100644 index 0000000..a996571 --- /dev/null +++ b/src/resources/resources.cpp @@ -0,0 +1,94 @@ +#include +#include +#include "../common/UsefulException.hpp" +#include "../common/ougge_format.hpp" +#include "resources.hpp" +#include "embedded_resources.h" + + +namespace ougge::resources { + +Resource::Resource(const std::string& path, const std::size_t size, StreamFactoryMethod open_read_steam_func) + : path(path), size(size), openStream(open_read_steam_func) +{} + +MemoryStreamBuf::MemoryStreamBuf(void* _p, const std::size_t n){ + char* p=(char*)_p; + setg(p, p, p + n); + setp(p, p + n); +} + +std::istream::pos_type MemoryStreamBuf::seekoff( + std::istream::off_type off, + std::ios_base::seekdir dir, + std::ios_base::openmode which) +{ + if (dir == std::ios_base::cur) + gbump(off); + else if (dir == std::ios_base::end) + setg(eback(), egptr() + off, egptr()); + else if (dir == std::ios_base::beg) + setg(eback(), eback() + off, egptr()); + return gptr() - eback(); +} + +MemoryStreamRead::MemoryStreamRead(const void* p, const std::size_t n) + : std::istream(new MemoryStreamBuf((void*)p, n)) +{} + +MemoryStreamRead::~MemoryStreamRead(){ + delete rdbuf(); +} + +static std::unordered_map* _resourceMap = nullptr; + +void loadEmbeddedresources(){ + for(int i = 0; i < EmbeddedResource_table_count; i++){ + const EmbeddedResource& e = EmbeddedResource_table[i]; + std::cout <<"loading resource '" << e.path << "' " + << formatSizeHumanReadable(e.size) << std::endl; + + auto embedded_resource_factory = [e]() -> std::unique_ptr { + return std::make_unique(e.data, e.size); + }; + + auto r = _resourceMap->emplace(e.path, + Resource(e.path, e.size, embedded_resource_factory)); + if(!r.second) + throw UsefulException(ougge_format("can't load duplicate resource '%s'", e.path)); + } +} + +void init(){ + if(_resourceMap != nullptr) + throw UsefulException("resource has been initialized already"); + _resourceMap = new std::unordered_map(); + loadEmbeddedresources(); +} + +Resource& getResource(const std::string& path){ + auto it = _resourceMap->find(path); + if(it == _resourceMap->end()) + throw UsefulException(ougge_format("can't find resource '%s'", path.c_str())); + return it->second; +} + +std::string formatSizeHumanReadable(std::size_t b){ + std::stringstream ss; + ss.precision(3); + std::size_t k = b / 1024; + std::size_t m = k / 1024; + std::size_t g = m / 1024; + + if(g) + ss< +#include +#include + +namespace ougge::resources { + +// call this in main() +void init(); + +std::string formatSizeHumanReadable(std::size_t byte_n); + +class Resource { +public: + const std::string path; + const std::size_t size; + + using StreamFactoryMethod = std::function< std::unique_ptr () >; + + Resource(const std::string& path, const std::size_t size, StreamFactoryMethod open_read_steam_func); + + const StreamFactoryMethod openStream; +}; + +class MemoryStreamBuf : public std::streambuf { +public: + MemoryStreamBuf(void* p, const std::size_t n); + + virtual std::istream::pos_type seekoff( + std::istream::off_type off, + std::ios_base::seekdir dir, + std::ios_base::openmode which); +}; + +class MemoryStreamRead : public std::istream { +public: + MemoryStreamRead(const void* p, const std::size_t n); + + virtual ~MemoryStreamRead(); +}; + +Resource& getResource(const std::string& path); + +/// @brief stores requested resources in memory +/// @tparam T must implement constructor `T(const Resource&, ...)` +template +class CacheStorage { + std::unordered_map _map; + +public: + template + T& getOrCreate(const std::string& name, TCtorArgs&&... ctor_args){ + auto it = _map.find(name); + if(it != _map.end()) + return it->second; + auto& res = getResource(name); + auto e = _map.emplace(name, T(res, ctor_args...)); + return e.first->second; + } +}; +} diff --git a/src/resources/textures.cpp b/src/resources/textures.cpp new file mode 100644 index 0000000..2476a2b --- /dev/null +++ b/src/resources/textures.cpp @@ -0,0 +1,96 @@ +#include "textures.hpp" +#include +#include "../gui/gui_exceptions.hpp" + +namespace ougge::resources { + +Texture::Texture(const Resource& r, SDL_Renderer* renderer) + : Texture(*r.openStream(), r.size, renderer) +{} + +Texture::Texture(std::istream& s, size_t size, SDL_Renderer* renderer) + : renderer(renderer), texture(nullptr), w(0), h(0) +{ + SDL_RWops* sdl_stream = SDL_RWFromIStream(s, size); + if(!sdl_stream) + throw gui::SDLException(); + texture = std::shared_ptr(IMG_LoadTexture_RW(renderer, sdl_stream, 1), SDL_DestroyTexture); + if(!texture) + throw gui::IMGException(); + SDL_TRY(SDL_QueryTexture(texture.get(), nullptr, nullptr, &w, &h)); +} + +SDL_RenderCopyExF_Params::SDL_RenderCopyExF_Params() + : rotation_angle(0), flip(SDL_FLIP_NONE) +{} + +void Texture::render(const SDL_FRect& target_section){ + SDL_TRY( + SDL_RenderCopyF(renderer, texture.get(), nullptr, &target_section) + ); +} + +void Texture::render(const SDL_FRect& target_section, const SDL_Rect& texture_section){ + SDL_TRY( + SDL_RenderCopyF(renderer, texture.get(), &texture_section, &target_section) + ); +} + +void Texture::render(const SDL_RenderCopyExF_Params& p){ + SDL_TRY( + SDL_RenderCopyExF(renderer, texture.get(), + optional_value_ptr_or_null(p.texture_section), + optional_value_ptr_or_null(p.target_section), + -1.0f * angleToDegree(normalizeAngle(p.rotation_angle)), + optional_value_ptr_or_null(p.rotation_center), + p.flip + ) + ) +} + +static Sint64 istream_size(SDL_RWops* context){ + return (Sint64)(size_t)context->hidden.unknown.data2; +} + +static Sint64 istream_seek(SDL_RWops* context, Sint64 offset, i32 whence){ + std::istream* stream = (std::istream*)context->hidden.unknown.data1; + switch(whence){ + case SEEK_SET: stream->seekg(offset, std::ios::beg); break; + case SEEK_CUR: stream->seekg(offset, std::ios::cur); break; + case SEEK_END: stream->seekg(offset, std::ios::end); break; + default: break; + } + return stream->fail() ? -1 : (Sint64)stream->tellg(); +} + +static size_t istream_read(SDL_RWops* context, void *ptr, size_t size, size_t maxnum){ + if(size == 0) + return -1; + std::istream* stream = (std::istream*)context->hidden.unknown.data1; + stream->read((char*)ptr, size * maxnum); + + return stream->bad() ? -1 : stream->gcount() / size; +} + +static i32 istream_close(SDL_RWops* context){ + if (context) + SDL_FreeRW(context); + return 0; +} + +SDL_RWops* SDL_RWFromIStream(std::istream& stream, size_t size){ + SDL_RWops* rwops = SDL_AllocRW(); + if(rwops) { + rwops->size = istream_size; + rwops->seek = istream_seek; + rwops->read = istream_read; + rwops->write = nullptr; + rwops->close = istream_close; + rwops->hidden.unknown.data1 = &stream; + rwops->hidden.unknown.data2 = (void*)size; + rwops->type = SDL_RWOPS_UNKNOWN; + } + return rwops; +} + +} diff --git a/src/resources/textures.hpp b/src/resources/textures.hpp new file mode 100644 index 0000000..2c05b7c --- /dev/null +++ b/src/resources/textures.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include "resources.hpp" +#include "../common/math.hpp" + +namespace ougge::resources { + +#define SDL_RectConstruct(X, Y, W, H) (SDL_Rect){X, Y, W, H} +#define SDL_FRectConstruct(X, Y, W, H) (SDL_FRect){X, Y, W, H} +#define SDL_PointConstruct(X, Y) (SDL_Point){X, Y, W, H} +#define SDL_FPointConstruct(X, Y) (SDL_FPoint){X, Y, W, H} + +#define optional_value_ptr_or_null(OPT) (OPT ? &(*OPT) : nullptr) + +SDL_RWops* SDL_RWFromIStream(std::istream& stream, size_t size); + +struct SDL_RenderCopyExF_Params { + std::optional texture_section; + std::optional target_section; + std::optional rotation_center; + angle_t rotation_angle; + SDL_RendererFlip flip; + + SDL_RenderCopyExF_Params(); +}; + +struct Texture { + SDL_Renderer* renderer; + std::shared_ptr texture; + i32 w; + i32 h; + + Texture(const Resource& r, SDL_Renderer* renderer); + Texture(std::istream& s, size_t size, SDL_Renderer* renderer); + + void render(const SDL_FRect& target_section); + void render(const SDL_FRect& target_section, const SDL_Rect& texture_section); + void render(const SDL_RenderCopyExF_Params& params); +}; + +}