From 0da950caf95c4765de7bc0c9e3f5c41502f4e912 Mon Sep 17 00:00:00 2001 From: Daanish Date: Tue, 5 May 2026 21:09:52 +0100 Subject: [PATCH] Fix major incorrect usage of mrb class definitions causing segfaults --- Makefile | 4 +- include/app/app.h | 47 ++---- include/binding/definitions.h | 264 +++++++++++++++++++++++++++------- include/binding/ruby.h | 33 +++-- include/pch.h | 16 +++ include/utils.h | 77 +++++++--- include/window/window.h | 12 +- samples/button.rb | 19 ++- src/window/window.cc | 73 ++++++---- 9 files changed, 393 insertions(+), 152 deletions(-) diff --git a/Makefile b/Makefile index 378f041..279ce6a 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ CFLAGS_DEBUG := \ -std=c++20 -Wall -Wextra \ -O0 -g -fno-omit-frame-pointer \ -Wno-unused-command-line-argument \ - -I./include \ + -I./include -I./libs/mruby/include \ $(SDL_CFLAGS) CFLAGS_RELEASE := \ @@ -32,7 +32,7 @@ CFLAGS_RELEASE := \ -flto=thin \ -fno-rtti -fomit-frame-pointer -DNDEBUG -s \ -Wno-unused-command-line-argument \ - -I./include \ + -I./include -I./libs/mruby/include \ $(SDL_CFLAGS) PCH_CFLAGS_DEBUG := $(CFLAGS_DEBUG) -x c++-header diff --git a/include/app/app.h b/include/app/app.h index fd9fdd3..3885714 100644 --- a/include/app/app.h +++ b/include/app/app.h @@ -8,7 +8,6 @@ namespace app { struct Element { - int reference_count = 0; Vec2 position = {0, 0}; float rotation = 0; Vec2 scale = {1, 1}; @@ -21,11 +20,8 @@ struct Element { virtual Vec2 size(Window *window) = 0; void click() { - if (!mrb_nil_p(on_click.proc)) { - int x = mrb_gc_arena_save(Ruby::mrb); + if (!mrb_nil_p(on_click.proc)) on_click.call(); - mrb_gc_arena_restore(Ruby::mrb, x); - } } }; @@ -93,21 +89,15 @@ struct ERect : Element { } }; -inline Pool element_pool; +inline Pool element_pool; struct Scene { - bool unused = false; std::vector element_ids; Ruby::Block update; ~Scene() { - for (const int &id : element_ids) { - element_pool[id]->reference_count--; - if (element_pool[id]->reference_count <= 0) { - delete element_pool[id]; - element_pool.release(id); - } - } + for (const int &id : element_ids) + element_pool.release(id); } void add_element(int element_id) { @@ -116,31 +106,27 @@ struct Scene { element_ids.end(), element_id, [](auto &a, auto &b) { - return element_pool[a]->z < element_pool[b]->z || (element_pool[a]->z == element_pool[b]->z && a < b); + auto e_a = element_pool[a]; + auto e_b = element_pool[b]; + return e_a->z < e_b->z || (e_a->z == e_b->z && a < b); } ); - element_pool[element_id]->reference_count++; + element_pool.use(element_id); element_ids.insert(it, element_id); } void delete_element(int element_id) { auto it = std::find(element_ids.begin(), element_ids.end(), element_id); if (it != element_ids.end()) { - element_pool[element_id]->reference_count--; - if (element_pool[element_id]->reference_count <= 0) { - delete element_pool[element_id]; - element_pool.release(element_id); - } + element_pool.release(element_id); element_ids.erase(it); } } void update_scene(uint64_t delta_time) { if (!mrb_nil_p(update.proc)) { - int x = mrb_gc_arena_save(Ruby::mrb); mrb_value dt_val = mrb_int_value(Ruby::mrb, delta_time); update.call(1, &dt_val); - mrb_gc_arena_restore(Ruby::mrb, x); } } @@ -165,24 +151,15 @@ struct Scene { } }; -inline Pool scene_pool; +inline Pool scene_pool; struct App { int current_scene_id = 0; bool running = true; void switch_to_scene(int scene_id) { - int scene_pool_size = (int)scene_pool.all().size(); - for (int i = 0; i < scene_pool_size; ++i) { - auto &val = scene_pool[i]; - if (i == scene_id && val != nullptr && val->unused) - val->unused = false; - if (val != nullptr && val->unused) { - delete val; - scene_pool.release(i); - } - } - + scene_pool.release(current_scene_id); + scene_pool.use(scene_id); current_scene_id = scene_id; } diff --git a/include/binding/definitions.h b/include/binding/definitions.h index 66728a6..ad978f1 100644 --- a/include/binding/definitions.h +++ b/include/binding/definitions.h @@ -27,17 +27,63 @@ DEF_RB( }) ) +DEF_RB( + Image, + ({ window.unload_image(id); }), + ({ + char *path; + mrb_value kwargs; + mrb_get_args(mrb, "z|H", &path, &kwargs); + + float alpha = 1.0f; + bool pixel_art = false; + if (!mrb_nil_p(kwargs)) { + HASH_FLOAT(kwargs, alpha, alpha); + HASH_BOOL(kwargs, pixel_art, pixel_art); + } + + s->id = window.load_image(path, alpha, pixel_art); +#warning "Fix lifetimes for images/fonts right now they'll always exist" + }) +) + +DEF_RB( + Animation, + ({ + animation_pool.release(id); + }), + ({ + const mrb_value *frames_val; + mrb_int frames_len; + mrb_value kwargs; + mrb_get_args(mrb, "a|H", &frames_val, &frames_len, &kwargs); + + float fps = 1.0f; + if (!mrb_nil_p(kwargs)) + HASH_FLOAT(kwargs, fps, fps); + + std::vector frame_ids; + for (mrb_int i = 0; i < frames_len; i++) { + mrb_value frame_val = frames_val[i]; + if (!mrb_obj_is_kind_of(mrb, frame_val, mrb_class_get(mrb, "Image"))) { + mrb_raise(mrb, mrb_class_get(mrb, "Exception"), "Expected an array of Image objects"); + return mrb_nil_value(); + } + int frame_id = get_id_Image(mrb, frame_val); + window.use_image(frame_id); + frame_ids.push_back(frame_id); + } + + Animation *animation = new Animation{frame_ids, fps, true}; + s->id = animation_pool.acquire(animation); + animation_pool.use(s->id); + }) +) + DEF_RB( ElementText, ({ - app::Element *element = app::element_pool[id]; - if (element != nullptr) - element->reference_count--; - - if (element != nullptr && element->reference_count <= 0) { - delete element; - app::element_pool.release(id); - } + app::element_pool.release(id); }), ({ mrb_value font_val; @@ -48,7 +94,7 @@ DEF_RB( auto key_id = Localization::key_from_sym(mrb, key); if (!mrb_obj_is_kind_of(mrb, font_val, mrb_class_get(mrb, "Font"))) { - mrb_raise(mrb, mrb_class_get(Ruby::mrb, "Exception"), "Expected a Font object"); + mrb_raise(mrb, mrb_class_get(mrb, "Exception"), "Expected a Font object"); return mrb_nil_value(); } @@ -67,29 +113,32 @@ DEF_RB( Vec2 position = {x, y}; - int id = get_id(font_val); + int id = get_id_Font(mrb, font_val); + window.use_font(id); app::EText *element = new app::EText(key_id, id, {255, 255, 255, 255}); - element->reference_count++; element->position = position; element->z = z; element->click_through = click_through; s->id = app::element_pool.acquire(element); + app::element_pool.use(s->id); }) ) +static mrb_value element_text_on_click(mrb_state *mrb, mrb_value self) { + mrb_value block; + mrb_get_args(mrb, "&", &block); + int id = get_id_ElementText(mrb, self); + app::Element *element = app::element_pool[id]; + element->on_click.set_proc(block); + return self; +} + DEF_RB( ElementRect, ({ - app::Element *element = app::element_pool[id]; - if (element != nullptr) - element->reference_count--; - - if (element != nullptr && element->reference_count <= 0) { - delete element; - app::element_pool.release(id); - } + app::element_pool.release(id); }), ({ mrb_value shape_val; @@ -119,18 +168,27 @@ DEF_RB( (uint8_t)((color >> 8) & 0xFF), (uint8_t)(color & 0xFF)} ); - element->reference_count++; element->position = position; element->z = z; s->id = app::element_pool.acquire(element); + app::element_pool.use(s->id); }) ) +static mrb_value element_rect_on_click(mrb_state *mrb, mrb_value self) { + mrb_value block; + mrb_get_args(mrb, "&", &block); + int id = get_id_ElementRect(mrb, self); + app::Element *element = app::element_pool[id]; + element->on_click.set_proc(block); + return self; +} + static mrb_value element_rect_color_set(mrb_state *mrb, mrb_value self) { mrb_value color_val; mrb_get_args(mrb, "o", &color_val); - int id = get_id(self); + int id = get_id_ElementRect(mrb, self); app::ERect *element = (app::ERect *)app::element_pool[id]; element->color = { (uint8_t)((mrb_fixnum(color_val) >> 24) & 0xFF), @@ -141,17 +199,8 @@ static mrb_value element_rect_color_set(mrb_state *mrb, mrb_value self) { return self; } -static mrb_value element_on_click(mrb_state *mrb, mrb_value self) { - mrb_value block; - mrb_get_args(mrb, "&", &block); - int id = get_id(self); - app::Element *element = app::element_pool[id]; - element->on_click.set_proc(block); - return self; -} - -static mrb_value element_rect_color_get(mrb_state *, mrb_value self) { - int id = get_id(self); +static mrb_value element_rect_color_get(mrb_state *mrb, mrb_value self) { + int id = get_id_ElementRect(mrb, self); app::ERect *element = (app::ERect *)app::element_pool[id]; uint32_t color = ((uint32_t)element->color.r << 24) | ((uint32_t)element->color.g << 16) @@ -160,16 +209,65 @@ static mrb_value element_rect_color_get(mrb_state *, mrb_value self) { return mrb_fixnum_value(color); } +DEF_RB( + ElementImage, + ({ + app::element_pool.release(id); + }), + ({ + mrb_value animation_val; + mrb_value kwargs; + mrb_get_args(mrb, "o|H", &animation_val, &kwargs); + + if (!mrb_obj_is_kind_of(mrb, animation_val, mrb_class_get(mrb, "Animation"))) { + mrb_raise(mrb, mrb_class_get(mrb, "Exception"), "Expected an Animation object"); + return mrb_nil_value(); + } + + float x = 0; + float y = 0; + float z = 0; + bool click_through = false; + if (!mrb_nil_p(kwargs)) { + mrb_value pos_hash = HASH_GET(kwargs, position); + HASH_FLOAT(pos_hash, x, x); + HASH_FLOAT(pos_hash, y, y); + + HASH_FLOAT(kwargs, z, z); + HASH_BOOL(kwargs, click_through, click_through); + } + + int id = get_id_Animation(mrb, animation_val); + Animation *animation = animation_pool[id]; + + app::EImage *element = new app::EImage(*animation); + element->position = {x, y}; + element->z = z; + element->click_through = click_through; + + s->id = app::element_pool.acquire(element); + app::element_pool.use(s->id); + }) +) + +static mrb_value element_image_on_click(mrb_state *mrb, mrb_value self) { + mrb_value block; + mrb_get_args(mrb, "&", &block); + int id = get_id_ElementImage(mrb, self); + app::Element *element = app::element_pool[id]; + element->on_click.set_proc(block); + return self; +} + DEF_RB( Scene, ({ - app::Scene *scene = app::scene_pool[id]; - if (scene != nullptr) - scene->unused = true; + app::scene_pool.release(id); }), ({ app::Scene *loading_scene = new app::Scene{}; s->id = app::scene_pool.acquire(loading_scene); + app::scene_pool.use(s->id); }) ) @@ -180,23 +278,75 @@ static mrb_value scene_add_element(mrb_state *mrb, mrb_value self) { if ( !mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementText")) && !mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementRect")) + && !mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementImage")) ) { - mrb_raise(mrb, mrb_class_get(Ruby::mrb, "Exception"), "Expected an ElementText or ElementRect object"); + mrb_raise(mrb, mrb_class_get(mrb, "Exception"), "Expected an ElementText, ElementRect, or ElementImage object"); return mrb_nil_value(); } - int id = get_id(self); + int id = get_id_Scene(mrb, self); + app::Scene *scene = app::scene_pool[id]; + + // int element_id = get_id_Element(mrb, element_val); + + int element_id; + if (mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementText"))) { + element_id = get_id_ElementText(mrb, element_val); + } else if (mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementRect"))) { + element_id = get_id_ElementRect(mrb, element_val); + } else if (mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementImage"))) { + element_id = get_id_ElementImage(mrb, element_val); + } else { + mrb_raise(mrb, mrb_class_get(mrb, "Exception"), "Expected an ElementText, ElementRect, or ElementImage object"); + return mrb_nil_value(); + } + + scene->add_element(element_id); + + return self; +} + +static mrb_value scene_delete_element(mrb_state *mrb, mrb_value self) { + mrb_value element_val; + mrb_get_args(mrb, "o", &element_val); + + if ( + !mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementText")) + && !mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementRect")) + && !mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementImage")) + ) { + mrb_raise(mrb, mrb_class_get(mrb, "Exception"), "Expected an ElementText, ElementRect, or ElementImage object"); + return mrb_nil_value(); + } + + int id = get_id_Scene(mrb, self); app::Scene *scene = app::scene_pool[id]; int element_id; - if ( - mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementText")) - || mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementRect")) - ) { - element_id = get_id(element_val); - scene->add_element(element_id); + if (mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementText"))) { + element_id = get_id_ElementText(mrb, element_val); + } else if (mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementRect"))) { + element_id = get_id_ElementRect(mrb, element_val); + } else if (mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementImage"))) { + element_id = get_id_ElementImage(mrb, element_val); + } else { + mrb_raise(mrb, mrb_class_get(mrb, "Exception"), "Expected an ElementText, ElementRect, or ElementImage object"); + return mrb_nil_value(); } + scene->delete_element(element_id); + + return self; +} + +static mrb_value scene_on_update(mrb_state *mrb, mrb_value self) { + mrb_value block; + mrb_get_args(mrb, "&", &block); + + int id = get_id_Scene(mrb, self); + app::Scene *scene = app::scene_pool[id]; + scene->update.set_proc(block); + return self; } @@ -223,12 +373,13 @@ static mrb_value app_start(mrb_state *mrb, mrb_value) { mrb_get_args(mrb, "o", &scene_val); if (!mrb_obj_is_kind_of(mrb, scene_val, mrb_class_get(mrb, "Scene"))) { - mrb_raise(mrb, mrb_class_get(Ruby::mrb, "Exception"), "Expected a Scene object"); + mrb_raise(mrb, mrb_class_get(mrb, "Exception"), "Expected a Scene object"); return mrb_nil_value(); } - int id = get_id(scene_val); + int id = get_id_Scene(mrb, scene_val); app_->switch_to_scene(id); + app_->loop(); return mrb_nil_value(); @@ -239,11 +390,11 @@ static mrb_value app_switch_to(mrb_state *mrb, mrb_value) { mrb_get_args(mrb, "o", &scene_val); if (!mrb_obj_is_kind_of(mrb, scene_val, mrb_class_get(mrb, "Scene"))) { - mrb_raise(mrb, mrb_class_get(Ruby::mrb, "Exception"), "Expected a Scene object"); + mrb_raise(mrb, mrb_class_get(mrb, "Exception"), "Expected a Scene object"); return mrb_nil_value(); } - int id = get_id(scene_val); + int id = get_id_Scene(mrb, scene_val); app_->switch_to_scene(id); return mrb_nil_value(); @@ -284,11 +435,15 @@ static mrb_value app_language_set(mrb_state *mrb, mrb_value) { inline void setup() { DEF_CLASS(Font, MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1)); + DEF_CLASS(Image, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); + + DEF_CLASS(Animation, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); + DEF_CLASS(ElementText, MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1)); ADD_METHOD( ElementText, "on_click", - element_on_click, + element_text_on_click, MRB_ARGS_BLOCK() ); @@ -296,7 +451,7 @@ inline void setup() { ADD_METHOD( ElementRect, "on_click", - element_on_click, + element_rect_on_click, MRB_ARGS_BLOCK() ); ADD_METHOD( @@ -312,8 +467,19 @@ inline void setup() { MRB_ARGS_NONE() ); + DEF_CLASS(ElementImage, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); + ADD_METHOD( + ElementImage, + "on_click", + element_image_on_click, + MRB_ARGS_BLOCK() + ); + DEF_CLASS(Scene, MRB_ARGS_NONE()); ADD_METHOD(Scene, "<<", scene_add_element, MRB_ARGS_REQ(1)); + ADD_METHOD(Scene, "delete", scene_delete_element, MRB_ARGS_REQ(1)); + ADD_METHOD(Scene, "remove", scene_delete_element, MRB_ARGS_REQ(1)); + ADD_METHOD(Scene, "on_update", scene_on_update, MRB_ARGS_BLOCK()); DEF_MODULE(App); ADD_MODULE_METHOD(App, "run", app_run, MRB_ARGS_REQ(3)); diff --git a/include/binding/ruby.h b/include/binding/ruby.h index a27c5a5..6bd0976 100644 --- a/include/binding/ruby.h +++ b/include/binding/ruby.h @@ -12,8 +12,12 @@ #define HASH_FLOAT(h, key, out) \ do { \ mrb_value v = HASH_GET(h, key); \ - if (mrb_fixnum_p(v)) \ + if (mrb_float_p(v)) \ + out = mrb_float(v); \ + else if (mrb_fixnum_p(v)) \ out = (float)mrb_fixnum(v); \ + else \ + out = 0; \ } while (0) #define HASH_BOOL(h, key, out) \ @@ -40,11 +44,19 @@ struct Wrapper { }; \ static mrb_value NAME##_init(mrb_state *mrb, mrb_value self) { \ UNUSED(mrb); \ + Wrapper *old = (Wrapper *)DATA_PTR(self); \ + if (old) \ + mrb_free(mrb, old); \ + mrb_data_init(self, nullptr, &NAME##_type); \ Wrapper *s = new Wrapper(); \ FN_INIT; \ - DATA_PTR(self) = s; \ - DATA_TYPE(self) = &NAME##_type; \ + mrb_data_init(self, s, &NAME##_type); \ return self; \ + } \ + inline int get_id_##NAME(mrb_state *mrb, mrb_value self) { \ + Wrapper *wrapper; \ + Data_Get_Struct(mrb, self, &NAME##_type, wrapper); \ + return wrapper->id; \ } #define ADD_METHOD(NAME, METHOD, FUNC, ARGS) \ @@ -52,6 +64,7 @@ struct Wrapper { #define DEF_CLASS(NAME, ARGS) \ RClass *NAME##_class = mrb_define_class(Ruby::mrb, #NAME, Ruby::mrb->object_class); \ + MRB_SET_INSTANCE_TT(NAME##_class, MRB_TT_CDATA); \ ADD_METHOD(NAME, "initialize", NAME##_init, ARGS); #define DEF_MODULE(NAME) \ @@ -60,10 +73,6 @@ struct Wrapper { #define ADD_MODULE_METHOD(MODULE, METHOD, FUNC, ARGS) \ mrb_define_class_method(Ruby::mrb, MODULE##_module, METHOD, FUNC, ARGS); -inline int get_id(mrb_value self) { - return ((Wrapper *)DATA_PTR(self))->id; -} - namespace Ruby { inline mrb_state *mrb = mrb_open(); @@ -71,7 +80,7 @@ struct Block { mrb_value proc; Block(mrb_value proc) : proc(proc) { - mrb_gc_register(mrb, proc); + mrb_gc_protect(mrb, proc); } Block() : proc(mrb_nil_value()) {} @@ -81,7 +90,7 @@ struct Block { mrb_gc_unregister(mrb, proc); proc = new_proc; if (!mrb_nil_p(proc)) - mrb_gc_register(mrb, proc); + mrb_gc_protect(mrb, proc); } Block(const Block &) = delete; @@ -90,7 +99,8 @@ struct Block { mrb_value call(int argc = 0, mrb_value *argv = nullptr) const { if (mrb_nil_p(proc)) return mrb_nil_value(); - return mrb_funcall(mrb, proc, "call", argc, argv); + mrb_value result = mrb_funcall_argv(mrb, proc, mrb_intern_cstr(mrb, "call"), argc, argv); + return result; } ~Block() { @@ -110,7 +120,10 @@ DEF_SYM(bold) DEF_SYM(italic) DEF_SYM(underline) DEF_SYM(position) +DEF_SYM(alpha) +DEF_SYM(fps) DEF_SYM(click_through) +DEF_SYM(call) DEF_SYM(x) DEF_SYM(y) DEF_SYM(w) diff --git a/include/pch.h b/include/pch.h index 956330b..1a4161d 100644 --- a/include/pch.h +++ b/include/pch.h @@ -3,13 +3,29 @@ #include "mruby.h" #include "mruby/array.h" +#include "mruby/boxing_word.h" +#include "mruby/class.h" +#include "mruby/common.h" #include "mruby/compile.h" #include "mruby/data.h" +#include "mruby/dump.h" +#include "mruby/error.h" +#include "mruby/gc.h" #include "mruby/hash.h" +#include "mruby/internal.h" #include "mruby/irep.h" +#include "mruby/numeric.h" +#include "mruby/object.h" +#include "mruby/opcode.h" +#include "mruby/presym.h" +#include "mruby/proc.h" +#include "mruby/range.h" +#include "mruby/re.h" #include "mruby/string.h" +#include "mruby/throw.h" #include "mruby/value.h" #include "mruby/variable.h" +#include "mruby/version.h" #include #include diff --git a/include/utils.h b/include/utils.h index 5ac90a8..67f6baa 100644 --- a/include/utils.h +++ b/include/utils.h @@ -152,38 +152,71 @@ struct Rect { } }; -template +template struct Pool { - T &operator[](int index) { - return items[index]; - } - - const std::vector &all() { - return items; - } - - int acquire(const T &item) { - if (!free_indices.empty()) { - int index = free_indices.back(); - free_indices.pop_back(); - items[index] = std::move(item); - return index; - } else { - items.push_back(std::move(item)); - return items.size() - 1; + T *operator[](int index) { + if (!is_valid(index)) { + fprintf(stderr, "Invalid pool index: %d\n", index); + return nullptr; } + return slots[index].ptr; + } + + const std::vector &all() { + static std::vector all_items; + all_items.clear(); + for (const auto &slot : slots) + if (slot.alive) + all_items.push_back(slot.ptr); + return all_items; + } + + int acquire(T *item) { + int index; + if (!free_indices.empty()) { + index = free_indices.back(); + free_indices.pop_back(); + } else { + index = slots.size(); + slots.push_back({}); + } + slots[index].ptr = item; + slots[index].refcount = 1; + slots[index].alive = true; + return index; + } + + void use(int index) { + if (is_valid(index)) + slots[index].refcount++; } void release(int index) { - free_indices.push_back(index); + if (!is_valid(index)) + return; + auto &slot = slots[index]; + slot.refcount--; + if (slot.refcount <= 0) { + delete slot.ptr; + slot.ptr = nullptr; + slot.alive = false; + free_indices.push_back(index); + } } - bool is_valid(size_t index) const { - return index >= 0 && index < items.size() && std::find(free_indices.begin(), free_indices.end(), index) == free_indices.end(); + bool is_valid(int index) const { + return index >= 0 + && index < (int)slots.size() + && slots[index].alive; } private: - std::vector items; + struct Slot { + T *ptr = nullptr; + int refcount = 0; + bool alive = false; + }; + std::vector slots; std::vector free_indices; }; diff --git a/include/window/window.h b/include/window/window.h index bc42b8f..b12ee76 100644 --- a/include/window/window.h +++ b/include/window/window.h @@ -14,6 +14,11 @@ struct Font { bool pixel_art; }; +struct Text { + SDL_Texture *texture; + Vec2 size; +}; + struct Event { enum Type { NONE, @@ -57,8 +62,11 @@ struct Animation { std::vector frames; float frame_rate; bool loop; + int reference_count = 0; }; +inline Pool animation_pool; + struct Window { public: // title can be free'd by the caller after the window is created, as it's copied internally. @@ -70,10 +78,12 @@ public: // Rendering functions int load_image(const char *path, float alpha = 1.0f, bool pixel_art = false); + void use_image(int image_id); void unload_image(int image_id); Vec2 get_image_size(int image_id); int load_font(const char *path, int font_size, bool bold = false, bool italic = false, bool underline = false, bool pixel_art = false); + void use_font(int font_id); void unload_font(int font_id); // text is copied internally, so it can be free'd by the caller after the text is created. @@ -135,7 +145,7 @@ private: Pool font_pool; // Text pool - Pool text_pool; + Pool text_pool; struct RenderInstruction { enum Type { diff --git a/samples/button.rb b/samples/button.rb index 40c8774..4fe94b4 100644 --- a/samples/button.rb +++ b/samples/button.rb @@ -1,8 +1,14 @@ -App.run 400, 400, "Button Test" +App.run 720, 480, "Button Test" main_scene = Scene.new + default_font = Font.new "assets/fonts/charybdis.ttf", 24, pixel_art: true +bg_image1 = Image.new "assets/images/menu_bg.png", alpha: 0.7, pixel_art: true +bg_image2 = Image.new "assets/images/game_over_dead_bg.png", alpha: 1, pixel_art: true + +bg_animation = Animation.new [bg_image1, bg_image2], fps: 5 + App.language = :en App.localize(:en, :btn_text, "Click me!") @@ -10,6 +16,8 @@ text = ElementText.new :btn_text, default_font, position: {x: 50, y: 50}, z: 1, rect = ElementRect.new({x: 50, y: 50, w: 100, h: 25}, 0xFF0000FF, z: 0) +image_element = ElementImage.new bg_animation, position: {x: 0, y: 0}, z: -1 + rect.on_click do rect.color = rect.color == 0xFF0000FF ? 0x00FF00FF @@ -17,14 +25,15 @@ rect.on_click do 0xFF0000FF end -=begin -main_scene.update do |dt| - puts "Delta time: #{dt}ms" +main_scene.on_update do |delta_time| + # puts "Delta time: #{delta_time}ms" + # commented out to avoid spamming the console, but you can uncomment to see the delta time being printed every frame + # runs at 60fps, so you should see around 16-17ms being printed end -=end main_scene << text main_scene << rect +main_scene << image_element # main_scene.delete(text) # Uncomment to test element deletion diff --git a/src/window/window.cc b/src/window/window.cc index bc054b5..02ff077 100644 --- a/src/window/window.cc +++ b/src/window/window.cc @@ -42,16 +42,13 @@ void Window::update(Vec2 new_size, const char *new_title) { Window::~Window() { for (auto &tex : image_pool.all()) - if (tex.texture) - SDL_DestroyTexture(tex.texture); + SDL_DestroyTexture(tex->texture); for (auto tex : text_pool.all()) - if (tex) - SDL_DestroyTexture(tex); + SDL_DestroyTexture(tex->texture); for (auto &f : font_pool.all()) - if (f.font) - TTF_CloseFont(f.font); + TTF_CloseFont(f->font); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); @@ -76,7 +73,15 @@ int Window::load_image(const char *path, float alpha, bool pixel_art) { float w, h; SDL_GetTextureSize(texture, &w, &h); - return image_pool.acquire({texture, {w, h}}); + Image *img = new Image{texture, {w, h}}; + int id = image_pool.acquire(img); + image_pool.use(id); + + return id; +} + +void Window::use_image(int image_id) { + image_pool.use(image_id); } Vec2 Window::get_image_size(int image_id) { @@ -84,7 +89,7 @@ Vec2 Window::get_image_size(int 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}; + return image_pool[image_id]->size; } void Window::unload_image(int image_id) { @@ -93,7 +98,7 @@ void Window::unload_image(int image_id) { return; } - SDL_DestroyTexture(image_pool[image_id].texture); + SDL_DestroyTexture(image_pool[image_id]->texture); image_pool.release(image_id); } @@ -115,7 +120,15 @@ int Window::load_font(const char *path, int font_size, bool bold, bool italic, b TTF_SetFontStyle(font, style); - return font_pool.acquire({font, pixel_art}); + Font *f = new Font{font, pixel_art}; + int id = font_pool.acquire(f); + font_pool.use(id); + + return id; +} + +void Window::use_font(int font_id) { + font_pool.use(font_id); } void Window::unload_font(int font_id) { @@ -124,7 +137,7 @@ void Window::unload_font(int font_id) { return; } - TTF_CloseFont(font_pool[font_id].font); + TTF_CloseFont(font_pool[font_id]->font); font_pool.release(font_id); } @@ -135,7 +148,7 @@ int Window::create_text(int font_id, const char *text, uint32_t length, Color co } SDL_Color sdl_color = {color.r, color.g, color.b, color.a}; - SDL_Surface *surface = TTF_RenderText_Solid(font_pool[font_id].font, text, length, sdl_color); + SDL_Surface *surface = TTF_RenderText_Solid(font_pool[font_id]->font, text, length, sdl_color); if (surface == nullptr) { fprintf(stderr, "Failed to create text surface: %s\n", SDL_GetError()); @@ -150,10 +163,17 @@ int Window::create_text(int font_id, const char *text, uint32_t length, Color co return UINT32_MAX; } - if (font_pool[font_id].pixel_art) + if (font_pool[font_id]->pixel_art) SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); - return text_pool.acquire(texture); + float w, h; + SDL_GetTextureSize(texture, &w, &h); + + Text *t = new Text{texture, {w, h}}; + int id = text_pool.acquire(t); + text_pool.use(id); + + return id; } Vec2 Window::get_text_size(int text_id) { @@ -161,9 +181,7 @@ Vec2 Window::get_text_size(int 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}; + return text_pool[text_id]->size; } void Window::unload_text(int text_id) { @@ -172,7 +190,7 @@ void Window::unload_text(int text_id) { return; } - SDL_DestroyTexture(text_pool[text_id]); + SDL_DestroyTexture(text_pool[text_id]->texture); text_pool.release(text_id); } @@ -275,7 +293,8 @@ void Window::render() { for (const RenderInstruction &instruction : render_instructions) { switch (instruction.type) { case RenderInstruction::DRAW_TEXT: { - SDL_Texture *texture = text_pool[instruction.draw_text.text_id]; + Text *text = text_pool[instruction.draw_text.text_id]; + SDL_Texture *texture = text->texture; SDL_FlipMode flip = SDL_FLIP_NONE; @@ -287,14 +306,12 @@ void Window::render() { float scale_x = std::abs(instruction.scale.x) * scale; float scale_y = std::abs(instruction.scale.y) * scale; - float w, h; - SDL_GetTextureSize(texture, &w, &h); - SDL_FRect dst_rect; dst_rect.x = instruction.draw_text.position.x * scale + offset.x; dst_rect.y = instruction.draw_text.position.y * scale + offset.y; - dst_rect.w = scale_x * w; - dst_rect.h = scale_y * h; + + dst_rect.w = scale_x * text->size.x; + dst_rect.h = scale_y * text->size.y; SDL_RenderTextureRotated( renderer, @@ -308,7 +325,7 @@ void Window::render() { } break; case RenderInstruction::DRAW_IMAGE: { - Image image = image_pool[instruction.draw_image.image_id]; + Image *image = image_pool[instruction.draw_image.image_id]; SDL_FlipMode flip = SDL_FLIP_NONE; @@ -323,12 +340,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 * image.size.x; - dst_rect.h = scale_y * image.size.y; + dst_rect.w = scale_x * image->size.x; + dst_rect.h = scale_y * image->size.y; SDL_RenderTextureRotated( renderer, - image.texture, + image->texture, nullptr, &dst_rect, instruction.angle,