diff --git a/src/Engine.cpp b/src/Engine.cpp new file mode 100644 index 0000000..3469a33 --- /dev/null +++ b/src/Engine.cpp @@ -0,0 +1,114 @@ +#include "Engine.hpp" +#include "gui/gui.hpp" + +namespace ougge { + +Engine::Engine() + : gameObjectPool(GAMEOBJECTPOOL_SIZE), textures(&resourceManager) +{ +} + +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, resourceManager); +} + +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::ResourceManager resourceManager; + 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/common/UsefulException.cpp b/src/common/UsefulException.cpp new file mode 100644 index 0000000..5e9e8ac --- /dev/null +++ b/src/common/UsefulException.cpp @@ -0,0 +1,15 @@ +#include "UsefulException.hpp" +#include + +UsefulException_::UsefulException_(const std::string& _message, const std::string& _file, const std::string& _func, int _line_n) + : message(_message), file(_file), function(_func), line_n(_line_n) +{ + std::stringstream ss; + ss< +#include + +#define UsefulException(MSG) UsefulException_(MSG, __FILE__, __func__, __LINE__) + +class UsefulException_ : public std::exception { + std::string message; + std::string file; + std::string function; + int line_n; + std::string complete_text; + +public: + UsefulException_(const std::string& msg, const std::string& _file, const std::string& _func, int line_n); + + virtual char const* what() const noexcept; +}; + +#define useful_assert(EXPR, ERRMSG) if(!(EXPR)) throw UsefulException(ERRMSG); 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/common/math.hpp b/src/common/math.hpp new file mode 100644 index 0000000..a0a7128 --- /dev/null +++ b/src/common/math.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "std.hpp" +#include + +#ifndef M_PI + #define M_E 2.7182818284590452354 + #define M_LOG2E 1.4426950408889634074 + #define M_LOG10E 0.43429448190325182765 + #define M_LN2 0.69314718055994530942 + #define M_LN10 2.30258509299404568402 + #define M_PI 3.14159265358979323846 + #define M_PI_2 1.57079632679489661923 + #define M_PI_4 0.78539816339744830962 + #define M_1_PI 0.31830988618379067154 + #define M_2_PI 0.63661977236758134308 + #define M_2_SQRTPI 1.12837916709551257390 + #define M_SQRT2 1.41421356237309504880 + #define M_SQRT1_2 0.70710678118654752440 +#endif + +struct Vec2 { + f32 x = 0, y = 0; +}; + +struct Vec3 { + f32 x = 0, y = 0, z = 0; +}; + +struct Vec4 { + f32 x = 0, y = 0, z = 0, w = 0; +}; + +/// radians +typedef f32 angle_t; + +static inline angle_t normalizeAngle(angle_t a){ + return std::fmodf(a + M_PI, 2*M_PI) - M_PI; +} + +static inline f32 angleToDegree(angle_t a){ + return (a / M_PI) * 180; +} + +static inline angle_t degreeToRadian(f32 d){ + return (d / 180) * M_PI; +} \ No newline at end of file diff --git a/src/common/ougge_format.cpp b/src/common/ougge_format.cpp new file mode 100644 index 0000000..f3f7642 --- /dev/null +++ b/src/common/ougge_format.cpp @@ -0,0 +1,84 @@ +#include +#include +#include "ougge_format.hpp" +#include "UsefulException.hpp" + +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; + + for(size_t i = 0; i < format_str.length(); i++){ + char c = format_str[i]; + + // format specifier + if(c == '%'){ + c = format_str[++i]; + bool l = false; + while(c == 'l'){ + l = true; + c = format_str[++i]; + } + + switch(c){ + case 'u': + if(l) ss<<(u64)va_arg(vl, u64); + else ss<<(u32)va_arg(vl, u32); + break; + case 'i': + case 'd': + if(l) ss<<(i64)va_arg(vl, i64); + else ss<<(i32)va_arg(vl, i32); + break; + case 'f': + // f32 is promoted to f64 when passed through '...' + ss<<(f64)va_arg(vl, f64); + break; + case 'p': + ss<<(void*)va_arg(vl, void*); + break; + case 'x': + if(l) ss<"; + break; + } + case 'c': + ss<<(char)va_arg(vl,int); + break; + default: + throw UsefulException("invalid format cpecifier"); + } + } + + // regular character + else ss< +#include +#include + +typedef int8_t i8; +typedef uint8_t u8; +typedef int16_t i16; +typedef uint16_t u16; +typedef int32_t i32; +typedef uint32_t u32; +typedef int64_t i64; +typedef uint64_t u64; +typedef float f32; +typedef double f64; +/// anonymous pointer without specified freeMembers() func +typedef void* Pointer; + +#define nameof(V) #V + +#ifdef _MSC_VER + #pragma comment(lib, "mincore_downlevel.lib") // Support OS older than SDK + #define _CRT_SECURE_NO_WARNINGS 1 + #define EXPORT __declspec(dllexport) + #define CALL __cdecl +#elif defined(__GNUC__) + #define EXPORT __attribute__((visibility("default"))) + #if __SIZEOF_POINTER__ == 4 + #define CALL __attribute__((__cdecl__)) + #else + #define CALL + #endif + #ifndef typeof + #define typeof(X) __typeof__(X) + #endif +#else + #pragma GCC error "unknown compiler" +#endif + +#define __count_args( \ + a0, a1, a2, a3, a4, a5, a6, a7 , \ + a8, a9, a10,a11,a12,a13,a14,a15, \ + a16,a17,a18,a19,a20,a21,a22,a23, \ + a24,a25,a26,a27,a28,a29,a30,a31, \ + a32,a33,a34,a35,a36,a37,a38,a39, \ + a40,a41,a42,a43,a44,a45,a46,a47, \ + a48,a49,a50,a51,a52,a53,a54,a55, \ + a56,a57,a58,a59,a60,a61,a62,a63, \ + a64,...) a64 +// Macro for counting variadic arguments (max 64) +// (see usage in kprint.h) +#define count_args(ARGS...) __count_args( \ + ARGS, \ + 64,63,62,61,60,59,58,57, \ + 56,55,54,53,52,51,50,49, \ + 48,47,46,45,44,43,42,41, \ + 40,39,38,37,36,35,34,33, \ + 32,31,30,29,28,27,26,25, \ + 24,23,22,21,20,19,18,17, \ + 16,15,14,13,12,11,10,9, \ + 8, 7, 6, 5, 4, 3, 2, 1, 0) diff --git a/src/common/time.cpp b/src/common/time.cpp new file mode 100644 index 0000000..318b234 --- /dev/null +++ b/src/common/time.cpp @@ -0,0 +1,19 @@ +// posix version definition to use clock_gettime +#ifndef _XOPEN_SOURCE + #if __STDC_VERSION__ >= 199901L + #define _XOPEN_SOURCE 600 + #else + #define _XOPEN_SOURCE 500 + #endif +#endif + +#include "time.hpp" +#include +#include "UsefulException.hpp" + +nsec_t getMonotonicTimeNsec(){ + struct timespec t; + if(clock_gettime(CLOCK_MONOTONIC, &t) != 0) + throw UsefulException("clock_gettime(CLOCK_MONOTONIC) error"); + return (nsec_t)t.tv_sec * 1e9 + (nsec_t)t.tv_nsec; +} diff --git a/src/common/time.hpp b/src/common/time.hpp new file mode 100644 index 0000000..70de9ab --- /dev/null +++ b/src/common/time.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "std.hpp" + +/// nanoseconds +typedef i64 nsec_t; + +/// can be used to measure delta time +///@return time from some moment in nanoseconds. +nsec_t getMonotonicTimeNsec(); + +#define optime(N, LABEL, CODE) {\ + nsec_t b = getMonotonicTimeNsec();\ + for(u32 i = 0; i < (u32)N; i++) {\ + CODE ;\ + }\ + nsec_t e = getMonotonicTimeNsec();\ + nsec_t t = e-b;\ + std::cout<<"operation '"< +#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..d130517 --- /dev/null +++ b/src/gui/MainWindow.cpp @@ -0,0 +1,220 @@ +#include +#include +#include +#include "MainWindow.hpp" +#include "gui_exceptions.hpp" +#include "../common/ougge_format.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, resources::ResourceManager& resourceManager){ + 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..c2c7b67 --- /dev/null +++ b/src/gui/MainWindow.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include "../common/std.hpp" +#include "../common/time.hpp" +#include "../resources/resources.hpp" +#include "../resources/fonts.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_path "fonts/DroidSans.ttf" + +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, resources::ResourceManager& resourceManager); + 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/gui_exceptions.cpp b/src/gui/gui_exceptions.cpp new file mode 100644 index 0000000..2073b1e --- /dev/null +++ b/src/gui/gui_exceptions.cpp @@ -0,0 +1,23 @@ +#include "gui_exceptions.hpp" +#include +#include + +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) +{ + 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) +{ + IMG_ClearError(); +} + +} diff --git a/src/gui/gui_exceptions.hpp b/src/gui/gui_exceptions.hpp new file mode 100644 index 0000000..90845ee --- /dev/null +++ b/src/gui/gui_exceptions.hpp @@ -0,0 +1,27 @@ +#pragma once + + +#include "../common/std.hpp" +#include "../common/UsefulException.hpp" + +namespace ougge::gui { + +#define SDLException() SDLException_(__FILE__, __func__, __LINE__) + +class SDLException_ : public UsefulException_ { +public: + SDLException_(const std::string& _file, const std::string& _func, int line_n); +}; + + +#define IMGException() IMGException_(__FILE__, __func__, __LINE__) + +class IMGException_ : public UsefulException_ { +public: + IMGException_(const std::string& _file, const std::string& _func, int line_n); +}; + + +#define SDL_TRY(EXPR) if(EXPR) throw ::ougge::gui::SDLException(); + +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..cf5dd7d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,65 @@ +#define SDL_MAIN_HANDLED +#include +#include +#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.tryGetOrCreate("tutel.png", engine.mainWindow.sdl_renderer); + if(tutel == nullptr){ + throw new UsefulException("couldn't find resource 'tutel.png'"); + } + 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 { + std::cout<<"initialized resource loader"< void { + drawTutel(engine); + }; + + 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/MemorySteam.cpp b/src/resources/MemorySteam.cpp new file mode 100644 index 0000000..fea56e5 --- /dev/null +++ b/src/resources/MemorySteam.cpp @@ -0,0 +1,40 @@ +#include "MemoryStream.hpp" + +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); +}; + +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(); +} diff --git a/src/resources/MemoryStream.hpp b/src/resources/MemoryStream.hpp new file mode 100644 index 0000000..0f1ced9 --- /dev/null +++ b/src/resources/MemoryStream.hpp @@ -0,0 +1,8 @@ +#include + +class MemoryStreamRead : public std::istream { +public: + MemoryStreamRead(const void* p, const std::size_t n); + + virtual ~MemoryStreamRead(); +}; \ No newline at end of file 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..96cde0f --- /dev/null +++ b/src/resources/fonts.cpp @@ -0,0 +1,29 @@ +#include "fonts.hpp" + +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(ResourceFactory* res, f32 font_size, f32 dpi){ + ImGuiIO& io = ImGui::GetIO(); + font_size *= dpi; + ImFontConfig font_cfg = ImFontConfig(); + std::sprintf(font_cfg.Name, "%s %ipx", res->path.c_str(), (i32)font_size); + + 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..6ab1b08 --- /dev/null +++ b/src/resources/fonts.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include "../common/std.hpp" +#include "resources.hpp" + +namespace ougge::resources { + +ImFont* ImFont_LoadFromFile(const std::string& file_path, f32 font_size, f32 dpi); + +ImFont* ImFont_LoadFromResource(ResourceFactory* res, f32 font_size, f32 dpi); + +} diff --git a/src/resources/resources.cpp b/src/resources/resources.cpp new file mode 100644 index 0000000..5591582 --- /dev/null +++ b/src/resources/resources.cpp @@ -0,0 +1,64 @@ +#include +#include +#include "../common/UsefulException.hpp" +#include "../common/ougge_format.hpp" +#include "resources.hpp" +#include "embedded_resources.h" +#include "MemoryStream.hpp" + +namespace ougge::resources { + +ResourceFactory::ResourceFactory(const std::string& path, const std::size_t size, StreamFactoryMethod open_read_steam_func) + : path(path), size(size), openStream(open_read_steam_func) +{} + + +std::shared_ptr ResourceManager::_embeddedResourcesMap; + +void ResourceManager::loadEmbeddedResources(){ + if(_embeddedResourcesMap != nullptr) + return; + + _embeddedResourcesMap = std::make_shared(); + 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 = _embeddedResourcesMap->emplace(e.path, + ResourceFactory(e.path, e.size, embedded_resource_factory)); + if(!r.second) + throw UsefulException(ougge_format("can't load duplicate resource '%s'", e.path)); + } + + _resourceMaps.push_back(_embeddedResourcesMap); +} + +ResourceManager::ResourceManager(){ + loadEmbeddedResources(); +} + +ResourceFactory* ResourceManager::tryGetResource(const std::string& path){ + // reverse iteration of linked list + auto map_list_iterator = _resourceMaps.end(); + while(map_list_iterator != _resourceMaps.begin()){ + --map_list_iterator; + auto map = *map_list_iterator; + + auto resource_iterator = map->find(path); + + // resource not found + if(resource_iterator == map->end()) + return nullptr; + + return &resource_iterator->second; + } + + return nullptr; +} + +} diff --git a/src/resources/resources.hpp b/src/resources/resources.hpp new file mode 100644 index 0000000..2e6d8e6 --- /dev/null +++ b/src/resources/resources.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include "../common/std.hpp" +#include +#include +#include +#include +#include + +namespace ougge::resources { + +class ResourceFactory { +public: + const std::string path; + const std::size_t size; + + using StreamFactoryMethod = std::function< std::unique_ptr () >; + + ResourceFactory(const std::string& path, const std::size_t size, StreamFactoryMethod open_read_steam_func); + + const StreamFactoryMethod openStream; +}; + +using ResourceMap = std::unordered_map; + +class ResourceManager { + std::list> _resourceMaps; + static std::shared_ptr _embeddedResourcesMap; + void loadEmbeddedResources(); + +public: + ResourceManager(); + + void addResourceMap(std::shared_ptr m); + + std::shared_ptr loadResouceMap(); + void removeResourceMap(); + + ResourceFactory* tryGetResource(const std::string& path); +}; + +/// @brief stores requested resources in memory +/// @tparam T must implement constructor `T(ResourceFactory*, ...)` +template +class CacheStorage { + std::unordered_map _map; + ResourceManager* _resourceManager; + +public: + CacheStorage(ResourceManager*); + CacheStorage(CacheStorage&&) = delete; + CacheStorage(const CacheStorage&) = delete; + + template + T* tryGetOrCreate(const std::string& name, TCtorArgs&&... ctor_args){ + auto it = _map.find(name); + if(it != _map.end()) + return &it->second; + + ResourceFactory* res = _resourceManager->tryGetResource(name); + if(res == nullptr) + return nullptr; + + auto e = _map.emplace(name, T(res, ctor_args...)); + return &e.first->second; + } +}; + + +template inline CacheStorage::CacheStorage(ResourceManager* resourceManager) + : _resourceManager(resourceManager) +{} + +} diff --git a/src/resources/textures.cpp b/src/resources/textures.cpp new file mode 100644 index 0000000..ec6b672 --- /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(ResourceFactory* 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..4869f77 --- /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(ResourceFactory* 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); +}; + +}