From a51eb903c4aa1bf0e83107f23ec7d278f3cf905a Mon Sep 17 00:00:00 2001 From: Daanish Date: Mon, 4 May 2026 21:28:22 +0100 Subject: [PATCH] Fix main loop, add example and cleanup --- include/app/app.h | 64 +++++++++++++++++++++++-------------- include/binding/ruby.h | 2 +- include/utils.h | 9 ++---- include/window/window.h | 19 +++++++---- samples/button.rb | 8 +++-- samples/main.rb | 18 +++++------ src/main.cc | 70 +++++++---------------------------------- src/window/window.cc | 65 ++++++++++++++++++++++++++------------ 8 files changed, 127 insertions(+), 128 deletions(-) diff --git a/include/app/app.h b/include/app/app.h index 6e74087..6cc24b1 100644 --- a/include/app/app.h +++ b/include/app/app.h @@ -1,3 +1,6 @@ +#ifndef APP_H +#define APP_H + #include "binding/ruby.h" #include "localization/localization.h" #include "utils.h" @@ -25,11 +28,10 @@ struct Element { } }; -struct Image : Element { +struct EImage : Element { Animation animation; - float alpha = 1.0f; - Image(Animation animation) : animation(std::move(animation)) {} + EImage(Animation animation) : animation(std::move(animation)) {} Vec2 size(Window *window) override { if (animation.frames.empty()) @@ -42,24 +44,24 @@ struct Image : Element { return; int frame_index = (int)((abs_time * animation.frame_rate) / 1000) % animation.frames.size(); - window.draw(animation.frames[frame_index], position, z, rotation, scale, alpha); + window.draw(animation.frames[frame_index], position, z, rotation, scale); } }; -struct Text : Element { +struct EText : Element { Localization::Key key; int font_id; int font_size = 16; Color color = {255, 255, 255, 255}; - Text(Localization::Key key, int font_id, int font_size, Color color) + EText(Localization::Key key, int font_id, int font_size, Color color) : key(key), font_id(font_id), font_size(font_size), color(color) {} Vec2 size(Window *window) override { return window->get_text_size(text_id); }; - void render(Window &window, uint64_t abs_time) override { + void render(Window &window, uint64_t) override { if (last_language != Localization::current_language || last_key != key || text_id == -1) { std::string localized_str = Localization::get(key); if (text_id != -1) @@ -86,12 +88,12 @@ struct ERect : Element { ERect(Vec2 shape, Color color) : shape(shape), color(color) {} - void render(Window &window, uint64_t abs_time) override { + void render(Window &window, uint64_t) override { window.draw_rect(Rect::from_size(position, shape), color, z, rotation); } }; -extern Pool> element_pool; +inline Pool element_pool; struct Scene { std::vector element_ids; @@ -120,7 +122,7 @@ struct Scene { void click(Window *window, Vec2 position) { for (auto it = element_ids.rbegin(); it != element_ids.rend(); ++it) { - Element *e = element_pool[*it].get(); + Element *e = element_pool[*it]; Vec2 size = e->size(window); Rect rect = Rect::from_size(e->position, size * e->scale); if (rect.contains(position)) { @@ -139,31 +141,39 @@ struct Scene { } }; -extern Pool> scene_pool; +inline Pool scene_pool; struct App { - Window window; int current_scene_id = 0; bool running = true; - App(Vec2 size, const char *title, Animation loading_animation) - : window(size, title) { - std::unique_ptr loading_scene = std::make_unique(); + App(Vec2 size, const char *title, Animation loading_animation) { + window.update(size, title); #warning "Should try to center the loading image or scale it to fit the screen" - std::unique_ptr image = std::make_unique(loading_animation); - int image_id = element_pool.acquire(std::move(image)); + Scene *loading_scene = new Scene{}; + EImage *image = new EImage(loading_animation); + int image_id = element_pool.acquire(image); loading_scene->add_element(image_id); - scene_pool.acquire(std::move(loading_scene)); + scene_pool.acquire(loading_scene); } void loop() { + const uint64_t target_ms = 1000 / 60; + + uint64_t time_a = window.get_time(); + uint64_t time_b = window.get_time(); + while (running) { + time_a = window.get_time(); + uint64_t frame_time = time_a - time_b; + if (frame_time <= target_ms) + usleep((target_ms - frame_time) * 1000); + + time_b = window.get_time(); + uint64_t dt = window.delta_time(); - uint64_t abs_time = window.get_time(); - Scene *scene = scene_pool[current_scene_id].get(); - - scene->update_scene(dt); + Scene *scene = scene_pool[current_scene_id]; Event e; while (window.poll(e)) { @@ -173,6 +183,7 @@ struct App { break; case Event::MOUSE_BUTTON_DOWN: scene->click(&window, e.mouse_button.position); + printf("DT: %lu ms\n", dt); break; default: break; @@ -180,10 +191,15 @@ struct App { window.consume(e); } - scene->render(window, abs_time); + scene->update_scene(dt); + scene->render(window, window.get_time()); window.render(); + + window.clear(); } } }; -} // namespace app \ No newline at end of file +} // namespace app + +#endif \ No newline at end of file diff --git a/include/binding/ruby.h b/include/binding/ruby.h index ab1b9f4..a3c8793 100644 --- a/include/binding/ruby.h +++ b/include/binding/ruby.h @@ -2,7 +2,7 @@ #include "utils.h" namespace Ruby { -extern mrb_state *mrb; +inline mrb_state *mrb = mrb_open(); struct Block { mrb_value proc; diff --git a/include/utils.h b/include/utils.h index 1a3e550..269d71b 100644 --- a/include/utils.h +++ b/include/utils.h @@ -158,11 +158,6 @@ struct Rect { template struct Pool { - Pool() { - items.reserve(20); - free_indices.reserve(20); - } - T &operator[](int index) { return items[index]; } @@ -175,10 +170,10 @@ struct Pool { if (!free_indices.empty()) { int index = free_indices.back(); free_indices.pop_back(); - items[index] = item; + items[index] = std::move(item); return index; } else { - items.push_back(item); + items.push_back(std::move(item)); return items.size() - 1; } } diff --git a/include/window/window.h b/include/window/window.h index ae46b9f..bc42b8f 100644 --- a/include/window/window.h +++ b/include/window/window.h @@ -4,6 +4,11 @@ #include "pch.h" #include "utils.h" +struct Image { + SDL_Texture *texture; + Vec2 size; +}; + struct Font { TTF_Font *font; bool pixel_art; @@ -59,11 +64,12 @@ public: // title can be free'd by the caller after the window is created, as it's copied internally. Window(Vec2 size, const char *title); + void update(Vec2 new_size, const char *new_title); + ~Window(); // Rendering functions - - int load_image(const char *path, bool pixel_art = false); + int load_image(const char *path, float alpha = 1.0f, bool pixel_art = false); void unload_image(int image_id); Vec2 get_image_size(int image_id); @@ -77,7 +83,7 @@ public: bool write(int text_id, Vec2 position, float z, float angle = 0.0f, Vec2 scale = {1.0f, 1.0f}); - bool draw(int image_id, Vec2 position, float z, float angle = 0.0f, Vec2 scale = {1.0f, 1.0f}, float alpha = 1.0f); + bool draw(int image_id, Vec2 position, float z, float angle = 0.0f, Vec2 scale = {1.0f, 1.0f}); bool draw_rect(Rect rect, Color color, float z, float angle = 0.0f); @@ -123,10 +129,10 @@ private: SDL_Texture *white_tex; // Image pool - Pool image_pool; + Pool image_pool; // Font pool - Pool font_pool; + Pool font_pool; // Text pool Pool text_pool; @@ -146,7 +152,6 @@ private: struct { int image_id; Vec2 position; - float alpha; } draw_image; struct { @@ -172,4 +177,6 @@ private: void draw_letterbox(); }; +inline Window window({720, 480}, "Mysth Engine"); + #endif diff --git a/samples/button.rb b/samples/button.rb index 8dfa7ce..14fe9f9 100644 --- a/samples/button.rb +++ b/samples/button.rb @@ -1,4 +1,4 @@ -app = App.new 400, 400, title: "Button Test" +App.run 400, 400, title: "Button Test" main_scene = Scene.new @@ -15,7 +15,11 @@ rect.on_click do 0xFF0000 end +main_scene.update do |dt| + puts "Delta time: #{dt}ms" +end + main_scene << text main_scene << rect -app.start main_scene \ No newline at end of file +App.start main_scene \ No newline at end of file diff --git a/samples/main.rb b/samples/main.rb index 5ab6dea..6f36a55 100644 --- a/samples/main.rb +++ b/samples/main.rb @@ -5,15 +5,15 @@ width, height = 720, 480 image = Tiles.from("assets/loading.png").first -app = App.new width, height, title: "My Game", loading_bg: image # can be image or animation +App.run width, height, title: "My Game", loading_bg: image # can be image or animation -app.map_key ?w, :forward -app.map_key ?s, :backward -app.map_key ?a, :left -app.map_key ?d, :right +App.map_key ?w, :forward +App.map_key ?s, :backward +App.map_key ?a, :left +App.map_key ?d, :right -app.map_key ?h, :heal -app.map_key ?t, :place +App.map_key ?h, :heal +App.map_key ?t, :place menu_scene = Scene.new @@ -24,7 +24,7 @@ start_button = ElementText.new "Start Game", font: default_font, position: {x: 1 game_scene = Scene.new start_button.on_click do - app.start game_scene + App.start game_scene end menu_scene << start_button @@ -184,4 +184,4 @@ world.shader :fog do |shader| z = 100 # shaders always appear above normal sprites but z is used to sort them relative to each other, so a shader with z 100 will appear above a shader with z 50, but both will appear above normal sprites end -app.start menu_scene # first scene to show when the game starts, stops the loading bg +App.start menu_scene # first scene to show when the game starts, stops the loading bg diff --git a/src/main.cc b/src/main.cc index 0493e64..144ab98 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,67 +1,19 @@ -#include "window/window.h" +#include "app/app.h" + +using namespace app; int main() { - printf("Starting engine...\n"); + int loading_image = window.load_image("assets/images/menu_bg.png", 1.0f, true); - Window w({800, 600}, "Render Test"); + Animation loading_animation = { + .frames = {loading_image}, + .frame_rate = 1.0f, // Not animated, so frame rate doesn't matter + .loop = false + }; - int img = w.load_image("./assets/images/arrow.png", true); - int font = w.load_font( - "./assets/fonts/charybdis.ttf", - 24, - false, - false, - false, - true - ); + App app({720, 480}, "Mysth Engine", loading_animation); - int text = w.create_text(font, "Hello Engine", 12, {255, 255, 255, 255}); - - float t = 0.0f; - - bool running = true; - - printf("Entering main loop...\n"); - - while (running) { - Event e; - while (w.poll(e)) { - switch (e.type) { - case Event::QUIT: - printf("Exiting\n"); - running = false; - break; - case Event::KEY_DOWN: - printf("Key down: %d\n", e.key.key); - if (e.key.key == 41) { - printf("Exiting\n"); - running = false; - } - break; - default: - break; - } - w.consume(e); - } - - w.clear(); - - // animated position - t += 0.016f; - float x = 200 + std::sin(t) * 100; - float y = 150 + std::cos(t) * 100; - - // background rect (low z) - w.draw_rect({{0, 0}, {800, 600}}, {20, 20, 30, 255}, 0, 20.0f); - - // image (mid z) - w.draw(img, {x, y}, 1.0f, t * 50.0f, {2.0f, 2.0f}, 1.0f); - - // text (top z) - w.write(text, {0, 0}, 2.0f, 0.0f, {1.0f, 1.0f}); - - w.render(); - } + app.loop(); return 0; } \ No newline at end of file diff --git a/src/window/window.cc b/src/window/window.cc index e0dca51..720dfd8 100644 --- a/src/window/window.cc +++ b/src/window/window.cc @@ -29,10 +29,20 @@ Window::Window(Vec2 size, const char *title) { SDL_UpdateTexture(white_tex, nullptr, &pixel, sizeof(pixel)); }; +void Window::update(Vec2 new_size, const char *new_title) { + if (new_size.x > 0 && new_size.y > 0) { + virtual_size = new_size; + compute_transform(); + } + + if (new_title) + SDL_SetWindowTitle(window, new_title); +} + Window::~Window() { - for (auto tex : image_pool.all()) - if (tex) - SDL_DestroyTexture(tex); + for (auto &tex : image_pool.all()) + if (tex.texture) + SDL_DestroyTexture(tex.texture); for (auto tex : text_pool.all()) if (tex) @@ -48,7 +58,7 @@ Window::~Window() { SDL_Quit(); } -int Window::load_image(const char *path, bool pixel_art) { +int Window::load_image(const char *path, float alpha, bool pixel_art) { SDL_Texture *texture = IMG_LoadTexture(renderer, path); if (pixel_art) { @@ -60,7 +70,20 @@ int Window::load_image(const char *path, bool pixel_art) { return UINT32_MAX; } - return image_pool.acquire(texture); + SDL_SetTextureAlphaMod(texture, (uint8_t)(alpha * 255)); + + float w, h; + SDL_GetTextureSize(texture, &w, &h); + + return image_pool.acquire({texture, {w, h}}); +} + +Vec2 Window::get_image_size(int image_id) { + if (!image_pool.is_valid(image_id)) { + fprintf(stderr, "Invalid image ID: %d\n", image_id); + return {0, 0}; + } + return {image_pool[image_id].size.x, image_pool[image_id].size.y}; } void Window::unload_image(int image_id) { @@ -69,7 +92,7 @@ void Window::unload_image(int image_id) { return; } - SDL_DestroyTexture(image_pool[image_id]); + SDL_DestroyTexture(image_pool[image_id].texture); image_pool.release(image_id); } @@ -132,6 +155,16 @@ int Window::create_text(int font_id, const char *text, uint32_t length, Color co return text_pool.acquire(texture); } +Vec2 Window::get_text_size(int text_id) { + if (!text_pool.is_valid(text_id)) { + fprintf(stderr, "Invalid text ID: %d\n", text_id); + return {0, 0}; + } + float w, h; + SDL_GetTextureSize(text_pool[text_id], &w, &h); + return {w, h}; +} + void Window::unload_text(int text_id) { if (!text_pool.is_valid(text_id)) { fprintf(stderr, "Invalid text ID: %d\n", text_id); @@ -162,7 +195,7 @@ bool Window::write(int text_id, Vec2 position, float z, float angle, Vec2 return true; }; -bool Window::draw(int image_id, Vec2 position, float z, float angle, Vec2 scale, float alpha) { +bool Window::draw(int image_id, Vec2 position, float z, float angle, Vec2 scale) { if (!image_pool.is_valid(image_id)) { fprintf(stderr, "Invalid image ID: %d\n", image_id); return false; @@ -176,7 +209,6 @@ bool Window::draw(int image_id, Vec2 position, float z, float angle, Vec2 .draw_image = { .image_id = image_id, .position = position, - .alpha = alpha, }} ); @@ -236,9 +268,7 @@ void Window::render() { std::stable_sort( render_instructions.begin(), render_instructions.end(), - [](const RenderInstruction &a, const RenderInstruction &b) { - return a.z < b.z; - } + [](const RenderInstruction &a, const RenderInstruction &b) { return a.z < b.z; } ); for (const RenderInstruction &instruction : render_instructions) { @@ -277,10 +307,7 @@ void Window::render() { } break; case RenderInstruction::DRAW_IMAGE: { - SDL_Texture *texture = image_pool[instruction.draw_image.image_id]; - - float w, h; - SDL_GetTextureSize(texture, &w, &h); + Image image = image_pool[instruction.draw_image.image_id]; SDL_FlipMode flip = SDL_FLIP_NONE; @@ -295,14 +322,12 @@ void Window::render() { SDL_FRect dst_rect; dst_rect.x = instruction.draw_image.position.x * scale + offset.x; dst_rect.y = instruction.draw_image.position.y * scale + offset.y; - dst_rect.w = scale_x * w; - dst_rect.h = scale_y * h; - - SDL_SetTextureAlphaMod(texture, static_cast(instruction.draw_image.alpha * 255)); + dst_rect.w = scale_x * image.size.x; + dst_rect.h = scale_y * image.size.y; SDL_RenderTextureRotated( renderer, - texture, + image.texture, nullptr, &dst_rect, instruction.angle,