diff --git a/include/app/app.h b/include/app/app.h index c4b7b68..add83e3 100644 --- a/include/app/app.h +++ b/include/app/app.h @@ -68,40 +68,30 @@ struct EText : Element { Localization::Key key; uint64_t font_id = UINT64_MAX; Color color = {255, 255, 255}; - mrb_value params = mrb_nil_value(); + std::unordered_map params; ~EText() { if (text_id != UINT64_MAX) text_pool.release(text_id); if (font_id != UINT64_MAX) font_pool.release(font_id); - if (!mrb_nil_p(params)) - mrb_gc_unregister(Ruby::mrb, params); } - EText(Localization::Key key, uint64_t font_id, Color color, mrb_value params) - : Element(Type::TEXT), key(key), font_id(font_id), color(color), params(params) { - mrb_gc_register(Ruby::mrb, params); - font_pool.use(font_id); - } - - void reload_params() { - // Force re-render by resetting last_key (without affecting this frame) - // this can be changed to use a dirty flag or version counter if needed - last_key = UINT16_MAX; - } + EText(Localization::Key key, uint64_t font_id, Color color, std::unordered_map params) + : Element(Type::TEXT), key(key), font_id(font_id), color(color), params(params) {} Vec2 size() override { if (text_id == UINT64_MAX) return {0, 0}; return window.get_text_size(text_id); - }; + } void render(Window &window, uint64_t) override { if ( last_language != Localization::current_language || last_key != key || text_id == UINT64_MAX || last_color != color + || params_changed ) { std::string localized_str = Localization::get(Ruby::mrb, key, params); if (text_id != UINT64_MAX) @@ -119,6 +109,7 @@ private: Color last_color = color; Localization::LanguageCode last_language = UINT32_MAX; Localization::Key last_key = UINT16_MAX; + bool params_changed = true; }; struct ERect : Element { diff --git a/include/binding/definitions.h b/include/binding/definitions.h index cb16c0f..9b56080 100644 --- a/include/binding/definitions.h +++ b/include/binding/definitions.h @@ -16,6 +16,59 @@ inline mrb_value wrap(mrb_state *mrb, RClass *cls, const mrb_data_type *type, ui return obj; } +static void Params_free(mrb_state *mrb, void *ptr) { + UNUSED(mrb); + uint64_t id = ((Wrapper *)ptr)->id; + app::element_pool.release(id); + delete (Wrapper *)ptr; +} + +static const struct mrb_data_type Params_type = {"Params", Params_free}; + +static mrb_value Params_init(mrb_state *mrb, mrb_value self) { + Wrapper *old = (Wrapper *)DATA_PTR(self); + if (old) + mrb_free(mrb, old); + mrb_data_init(self, nullptr, &Params_type); + Wrapper *s = new Wrapper(); + + mrb_int id; + mrb_get_args(mrb, "z", &id); + + s->id = id; + + mrb_data_init(self, s, &Params_type); + return self; +} + +static mrb_value params_get(mrb_state *mrb, mrb_value self) { + mrb_sym key; + mrb_get_args(mrb, "n", &key); + + uint64_t id = ((Wrapper *)DATA_PTR(self))->id; + app::element_pool.use(id); + app::EText *element = (app::EText *)app::element_pool[id]; + + auto it = element->params.find(mrb_sym2name(mrb, key)); + if (it == element->params.end()) + return mrb_nil_value(); + return mrb_str_new_cstr(mrb, it->second.c_str()); +} + +static mrb_value params_set(mrb_state *mrb, mrb_value self) { + mrb_sym key; + mrb_value value; + mrb_get_args(mrb, "no", &key, &value); + + uint64_t id = ((Wrapper *)DATA_PTR(self))->id; + app::element_pool.use(id); + app::EText *element = (app::EText *)app::element_pool[id]; + + element->params[mrb_sym2name(mrb, key)] = mrb_str_to_cstr(mrb, mrb_obj_as_string(mrb, value)); + + return mrb_nil_value(); +} + static void Font_free(mrb_state *mrb, void *ptr) { UNUSED(mrb); uint64_t id = ((Wrapper *)ptr)->id; @@ -26,7 +79,6 @@ static void Font_free(mrb_state *mrb, void *ptr) { static const struct mrb_data_type Font_type = {"Font", Font_free}; static mrb_value Font_init(mrb_state *mrb, mrb_value self) { - UNUSED(mrb); Wrapper *old = (Wrapper *)DATA_PTR(self); if (old) mrb_free(mrb, old); @@ -89,7 +141,6 @@ static void Image_free(mrb_state *mrb, void *ptr) { static const struct mrb_data_type Image_type = {"Image", Image_free}; static mrb_value Image_init(mrb_state *mrb, mrb_value self) { - UNUSED(mrb); Wrapper *old = (Wrapper *)DATA_PTR(self); if (old) mrb_free(mrb, old); @@ -156,7 +207,6 @@ static const struct mrb_data_type Animation_type = { }; static mrb_value Animation_init(mrb_state *mrb, mrb_value self) { - UNUSED(mrb); Wrapper *old = (Wrapper *)DATA_PTR(self); if (old) mrb_free(mrb, old); @@ -202,6 +252,31 @@ inline uint64_t get_id_Animation(mrb_state *mrb, mrb_value self) { return wrapper->id; } +static mrb_value static_image_animation(mrb_state *mrb, mrb_value) { + mrb_value image_val; + mrb_get_args(mrb, "o", &image_val); + + if (!mrb_obj_is_kind_of(mrb, image_val, mrb_class_get(mrb, "Image"))) { + mrb_raise( + mrb, + mrb_class_get(mrb, "Exception"), + "Expected an Image object" + ); + return mrb_nil_value(); + } + + uint64_t frame_id = get_id_Image(mrb, image_val); + image_pool.use(frame_id); + + std::vector frames = {frame_id}; + Animation *animation = new Animation{frames, 1.0f}; + Wrapper *s = new Wrapper(); + s->id = animation_pool.acquire(animation); + animation_pool.use(s->id); + + return wrap(mrb, mrb_class_get(mrb, "Animation"), &Animation_type, s->id); +} + static mrb_value animation_equal(mrb_state *mrb, mrb_value self) { mrb_value other; mrb_get_args(mrb, "o", &other); @@ -296,7 +371,6 @@ static const struct mrb_data_type ElementText_type = { }; static mrb_value ElementText_init(mrb_state *mrb, mrb_value self) { - UNUSED(mrb); Wrapper *old = (Wrapper *)DATA_PTR(self); if (old) mrb_free(mrb, old); @@ -358,7 +432,18 @@ static mrb_value ElementText_init(mrb_state *mrb, mrb_value self) { (uint8_t)((color_i) & 0xFF) }; - app::EText *element = new app::EText(key_id, id, color, t_args); + std::unordered_map params; + if (!mrb_nil_p(t_args)) { + mrb_value keys = mrb_hash_keys(mrb, t_args); + for (mrb_int i = 0; i < RARRAY_LEN(keys); i++) { + mrb_value key = RARRAY_PTR(keys)[i]; + mrb_value val = mrb_hash_get(mrb, t_args, key); + params[mrb_str_to_cstr(mrb, mrb_obj_as_string(mrb, key))] = + mrb_str_to_cstr(mrb, mrb_obj_as_string(mrb, val)); + } + } + + app::EText *element = new app::EText(key_id, id, color, params); element->position = {x, y}; element->scale = {scale_x, scale_y}; element->rotation = rotation; @@ -459,6 +544,12 @@ static mrb_value element_text_font_get(mrb_state *mrb, mrb_value self) { return wrap(mrb, mrb_class_get(mrb, "Font"), &Font_type, element->font_id); } +static mrb_value element_text_params(mrb_state *mrb, mrb_value self) { + uint64_t id = get_id_ElementText(mrb, self); + app::element_pool.use(id); + return wrap(mrb, mrb_class_get(mrb, "Params"), &Params_type, id); +} + static mrb_value element_text_font_set(mrb_state *mrb, mrb_value self) { mrb_value font_val; mrb_get_args(mrb, "o", &font_val); @@ -501,19 +592,6 @@ static mrb_value element_text_color_set(mrb_state *mrb, mrb_value self) { return mrb_nil_value(); } -static mrb_value element_text_params(mrb_state *mrb, mrb_value self) { - uint64_t id = get_id_ElementText(mrb, self); - app::EText *element = (app::EText *)app::element_pool[id]; - return element->params; -} - -static mrb_value element_text_params_load(mrb_state *mrb, mrb_value self) { - uint64_t id = get_id_ElementText(mrb, self); - app::EText *element = (app::EText *)app::element_pool[id]; - element->reload_params(); - return mrb_nil_value(); -} - static mrb_value element_text_position_get(mrb_state *mrb, mrb_value self) { uint64_t id = get_id_ElementText(mrb, self); app::EText *element = (app::EText *)app::element_pool[id]; @@ -695,7 +773,6 @@ static const struct mrb_data_type ElementRect_type = { }; static mrb_value ElementRect_init(mrb_state *mrb, mrb_value self) { - UNUSED(mrb); Wrapper *old = (Wrapper *)DATA_PTR(self); if (old) mrb_free(mrb, old); @@ -1042,7 +1119,6 @@ static const struct mrb_data_type ElementImage_type = { }; static mrb_value ElementImage_init(mrb_state *mrb, mrb_value self) { - UNUSED(mrb); Wrapper *old = (Wrapper *)DATA_PTR(self); if (old) mrb_free(mrb, old); @@ -1371,7 +1447,6 @@ static void Scene_free(mrb_state *mrb, void *ptr) { static const struct mrb_data_type Scene_type = {"Scene", Scene_free}; static mrb_value Scene_init(mrb_state *mrb, mrb_value self) { - UNUSED(mrb); Wrapper *old = (Wrapper *)DATA_PTR(self); if (old) mrb_free(mrb, old); @@ -1633,7 +1708,6 @@ static mrb_value app_current_scene(mrb_state *mrb, mrb_value) { uint64_t id = app_->current_scene_id; if (id == UINT64_MAX) return mrb_nil_value(); - app::Scene *scene = app::scene_pool[id]; app::scene_pool.use(id); return wrap(mrb, mrb_class_get(mrb, "Scene"), &Scene_type, id); } @@ -1762,6 +1836,10 @@ static mrb_value app_exit(mrb_state *, mrb_value) { } inline void setup() { + DEF_CLASS(Params, MRB_ARGS_REQ(1)); + ADD_METHOD(Params, "[]", params_get, MRB_ARGS_REQ(1)); + ADD_METHOD(Params, "[]=", params_set, MRB_ARGS_REQ(2)); + DEF_CLASS(Font, MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1)); ADD_METHOD(Font, "==", font_equal, MRB_ARGS_REQ(1)); ADD_METHOD(Font, "eql?", font_equal, MRB_ARGS_REQ(1)); @@ -1775,6 +1853,7 @@ inline void setup() { ADD_METHOD(Image, "height", image_height, MRB_ARGS_NONE()); DEF_CLASS(Animation, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); + ADD_CLASS_METHOD(Animation, "from", static_image_animation, MRB_ARGS_REQ(1)); ADD_METHOD(Animation, "==", animation_equal, MRB_ARGS_REQ(1)); ADD_METHOD(Animation, "eql?", animation_equal, MRB_ARGS_REQ(1)); ADD_METHOD(Animation, "hash", animation_hash, MRB_ARGS_NONE()); @@ -1796,9 +1875,7 @@ inline void setup() { ADD_METHOD(ElementText, "color", element_text_color_get, MRB_ARGS_NONE()); ADD_METHOD(ElementText, "font=", element_text_font_set, MRB_ARGS_REQ(1)); ADD_METHOD(ElementText, "font", element_text_font_get, MRB_ARGS_NONE()); -#warning "Make text params a proper class instead of using a hash for text params" ADD_METHOD(ElementText, "params", element_text_params, MRB_ARGS_NONE()); - ADD_METHOD(ElementText, "params!", element_text_params_load, MRB_ARGS_NONE()); #warning "Make vector a proper class instead of using a hash for vectors" ADD_METHOD(ElementText, "position=", element_text_position_set, MRB_ARGS_REQ(1)); ADD_METHOD(ElementText, "position", element_text_position_get, MRB_ARGS_NONE()); diff --git a/include/binding/ruby.h b/include/binding/ruby.h index 18e2f49..c111dcf 100644 --- a/include/binding/ruby.h +++ b/include/binding/ruby.h @@ -35,9 +35,15 @@ struct Wrapper { uint64_t id; }; +#define ADD_FUNCTION(NAME, FUNC, ARGS) \ + mrb_define_method(Ruby::mrb, Ruby::mrb->object_class, NAME, FUNC, ARGS); + #define ADD_METHOD(NAME, METHOD, FUNC, ARGS) \ mrb_define_method(Ruby::mrb, NAME##_class, METHOD, FUNC, ARGS); +#define ADD_CLASS_METHOD(CLASS, METHOD, FUNC, ARGS) \ + mrb_define_class_method(Ruby::mrb, CLASS##_class, METHOD, FUNC, ARGS); + #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); \ diff --git a/include/localization/localization.h b/include/localization/localization.h index d3194b1..fbfefcc 100644 --- a/include/localization/localization.h +++ b/include/localization/localization.h @@ -71,7 +71,7 @@ inline void set(LanguageCode lang_code, Key key, const std::string &text) { localized_strings[join_keys(lang_code, key)] = parse_localized(text); } -inline std::string get(mrb_state *mrb, Key key, mrb_value hash) { +inline std::string get(mrb_state *mrb, Key key, const std::unordered_map ¶ms) { auto it = localized_strings.find(join_keys(current_language, key)); if (it == localized_strings.end()) return "Localization not set!"; @@ -85,15 +85,12 @@ inline std::string get(mrb_state *mrb, Key key, mrb_value hash) { if (!seg.is_var) { result += seg.text; } else { - mrb_sym sym = get_var_sym(mrb, seg.text); - if (mrb_nil_p(hash) || mrb_nil_p(mrb_hash_get(mrb, hash, mrb_symbol_value(sym)))) { + auto param_it = params.find(seg.text); + if (param_it == params.end()) { result += "%{" + seg.text + "}"; continue; } - mrb_value val = mrb_hash_get(mrb, hash, mrb_symbol_value(sym)); - if (mrb_nil_p(val)) - continue; - result += mrb_str_to_cstr(mrb, val); + result += param_it->second; } } diff --git a/samples/button.rb b/samples/button.rb index 4f2d972..90c8020 100644 --- a/samples/button.rb +++ b/samples/button.rb @@ -1,4 +1,4 @@ -App.run 720, 480, "Button Test", fps: 60 +App.run 720, 480, "Button Test", fps: 240 main_scene = Scene.new @@ -9,6 +9,8 @@ bg_image2 = Image.new "assets/images/game_over_dead_bg.png", pixel_art: true bg_animation = Animation.new [bg_image1, bg_image2], fps: 5 +bg_image1_a = Animation.from(bg_image1) + App.language = :en App.localize(:en, :btn_text, "Click %{name}!") @@ -38,10 +40,10 @@ rect.click do 0x00FF00 : 0xFF0000 + pp text.params[:name] text.params[:name] = text.params[:name] == "Me" ? "You" : "Me" - text.params! end App.map_key :mouse_left, :action_name @@ -54,9 +56,8 @@ App.update do |delta_time| c_ += delta_time c_c += 1 - # puts "Delta time: #{delta_time}s" # 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 + # puts "Delta time: #{delta_time}s" el = App.scene.elements_at(App.mouse_position)