Make localization parameters better, and add a single frame animation helper

This commit is contained in:
2026-05-21 19:01:44 +01:00
parent e1d18743b5
commit 582b2fcd9e
5 changed files with 122 additions and 50 deletions
+6 -15
View File
@@ -68,40 +68,30 @@ struct EText : Element {
Localization::Key key; Localization::Key key;
uint64_t font_id = UINT64_MAX; uint64_t font_id = UINT64_MAX;
Color color = {255, 255, 255}; Color color = {255, 255, 255};
mrb_value params = mrb_nil_value(); std::unordered_map<std::string, std::string> params;
~EText() { ~EText() {
if (text_id != UINT64_MAX) if (text_id != UINT64_MAX)
text_pool.release(text_id); text_pool.release(text_id);
if (font_id != UINT64_MAX) if (font_id != UINT64_MAX)
font_pool.release(font_id); 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) EText(Localization::Key key, uint64_t font_id, Color color, std::unordered_map<std::string, std::string> params)
: Element(Type::TEXT), key(key), font_id(font_id), color(color), params(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;
}
Vec2<float> size() override { Vec2<float> size() override {
if (text_id == UINT64_MAX) if (text_id == UINT64_MAX)
return {0, 0}; return {0, 0};
return window.get_text_size(text_id); return window.get_text_size(text_id);
}; }
void render(Window &window, uint64_t) override { void render(Window &window, uint64_t) override {
if ( if (
last_language != Localization::current_language last_language != Localization::current_language
|| last_key != key || text_id == UINT64_MAX || last_key != key || text_id == UINT64_MAX
|| last_color != color || last_color != color
|| params_changed
) { ) {
std::string localized_str = Localization::get(Ruby::mrb, key, params); std::string localized_str = Localization::get(Ruby::mrb, key, params);
if (text_id != UINT64_MAX) if (text_id != UINT64_MAX)
@@ -119,6 +109,7 @@ private:
Color last_color = color; Color last_color = color;
Localization::LanguageCode last_language = UINT32_MAX; Localization::LanguageCode last_language = UINT32_MAX;
Localization::Key last_key = UINT16_MAX; Localization::Key last_key = UINT16_MAX;
bool params_changed = true;
}; };
struct ERect : Element { struct ERect : Element {
+101 -24
View File
@@ -16,6 +16,59 @@ inline mrb_value wrap(mrb_state *mrb, RClass *cls, const mrb_data_type *type, ui
return obj; 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) { static void Font_free(mrb_state *mrb, void *ptr) {
UNUSED(mrb); UNUSED(mrb);
uint64_t id = ((Wrapper *)ptr)->id; 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 const struct mrb_data_type Font_type = {"Font", Font_free};
static mrb_value Font_init(mrb_state *mrb, mrb_value self) { static mrb_value Font_init(mrb_state *mrb, mrb_value self) {
UNUSED(mrb);
Wrapper *old = (Wrapper *)DATA_PTR(self); Wrapper *old = (Wrapper *)DATA_PTR(self);
if (old) if (old)
mrb_free(mrb, 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 const struct mrb_data_type Image_type = {"Image", Image_free};
static mrb_value Image_init(mrb_state *mrb, mrb_value self) { static mrb_value Image_init(mrb_state *mrb, mrb_value self) {
UNUSED(mrb);
Wrapper *old = (Wrapper *)DATA_PTR(self); Wrapper *old = (Wrapper *)DATA_PTR(self);
if (old) if (old)
mrb_free(mrb, 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) { static mrb_value Animation_init(mrb_state *mrb, mrb_value self) {
UNUSED(mrb);
Wrapper *old = (Wrapper *)DATA_PTR(self); Wrapper *old = (Wrapper *)DATA_PTR(self);
if (old) if (old)
mrb_free(mrb, old); mrb_free(mrb, old);
@@ -202,6 +252,31 @@ inline uint64_t get_id_Animation(mrb_state *mrb, mrb_value self) {
return wrapper->id; 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<uint64_t> 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) { static mrb_value animation_equal(mrb_state *mrb, mrb_value self) {
mrb_value other; mrb_value other;
mrb_get_args(mrb, "o", &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) { static mrb_value ElementText_init(mrb_state *mrb, mrb_value self) {
UNUSED(mrb);
Wrapper *old = (Wrapper *)DATA_PTR(self); Wrapper *old = (Wrapper *)DATA_PTR(self);
if (old) if (old)
mrb_free(mrb, 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) (uint8_t)((color_i) & 0xFF)
}; };
app::EText *element = new app::EText(key_id, id, color, t_args); std::unordered_map<std::string, std::string> 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->position = {x, y};
element->scale = {scale_x, scale_y}; element->scale = {scale_x, scale_y};
element->rotation = rotation; 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); 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) { static mrb_value element_text_font_set(mrb_state *mrb, mrb_value self) {
mrb_value font_val; mrb_value font_val;
mrb_get_args(mrb, "o", &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(); 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) { static mrb_value element_text_position_get(mrb_state *mrb, mrb_value self) {
uint64_t id = get_id_ElementText(mrb, self); uint64_t id = get_id_ElementText(mrb, self);
app::EText *element = (app::EText *)app::element_pool[id]; 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) { static mrb_value ElementRect_init(mrb_state *mrb, mrb_value self) {
UNUSED(mrb);
Wrapper *old = (Wrapper *)DATA_PTR(self); Wrapper *old = (Wrapper *)DATA_PTR(self);
if (old) if (old)
mrb_free(mrb, 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) { static mrb_value ElementImage_init(mrb_state *mrb, mrb_value self) {
UNUSED(mrb);
Wrapper *old = (Wrapper *)DATA_PTR(self); Wrapper *old = (Wrapper *)DATA_PTR(self);
if (old) if (old)
mrb_free(mrb, 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 const struct mrb_data_type Scene_type = {"Scene", Scene_free};
static mrb_value Scene_init(mrb_state *mrb, mrb_value self) { static mrb_value Scene_init(mrb_state *mrb, mrb_value self) {
UNUSED(mrb);
Wrapper *old = (Wrapper *)DATA_PTR(self); Wrapper *old = (Wrapper *)DATA_PTR(self);
if (old) if (old)
mrb_free(mrb, 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; uint64_t id = app_->current_scene_id;
if (id == UINT64_MAX) if (id == UINT64_MAX)
return mrb_nil_value(); return mrb_nil_value();
app::Scene *scene = app::scene_pool[id];
app::scene_pool.use(id); app::scene_pool.use(id);
return wrap(mrb, mrb_class_get(mrb, "Scene"), &Scene_type, 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() { 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)); DEF_CLASS(Font, MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1));
ADD_METHOD(Font, "==", font_equal, MRB_ARGS_REQ(1)); ADD_METHOD(Font, "==", font_equal, MRB_ARGS_REQ(1));
ADD_METHOD(Font, "eql?", 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()); ADD_METHOD(Image, "height", image_height, MRB_ARGS_NONE());
DEF_CLASS(Animation, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); 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, "==", animation_equal, MRB_ARGS_REQ(1));
ADD_METHOD(Animation, "eql?", 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()); 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, "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_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementText, "font", element_text_font_get, MRB_ARGS_NONE()); 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, 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" #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_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementText, "position", element_text_position_get, MRB_ARGS_NONE()); ADD_METHOD(ElementText, "position", element_text_position_get, MRB_ARGS_NONE());
+6
View File
@@ -35,9 +35,15 @@ struct Wrapper {
uint64_t id; 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) \ #define ADD_METHOD(NAME, METHOD, FUNC, ARGS) \
mrb_define_method(Ruby::mrb, NAME##_class, 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) \ #define DEF_CLASS(NAME, ARGS) \
RClass *NAME##_class = mrb_define_class(Ruby::mrb, #NAME, Ruby::mrb->object_class); \ RClass *NAME##_class = mrb_define_class(Ruby::mrb, #NAME, Ruby::mrb->object_class); \
MRB_SET_INSTANCE_TT(NAME##_class, MRB_TT_CDATA); \ MRB_SET_INSTANCE_TT(NAME##_class, MRB_TT_CDATA); \
+4 -7
View File
@@ -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); 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<std::string, std::string> &params) {
auto it = localized_strings.find(join_keys(current_language, key)); auto it = localized_strings.find(join_keys(current_language, key));
if (it == localized_strings.end()) if (it == localized_strings.end())
return "Localization not set!"; return "Localization not set!";
@@ -85,15 +85,12 @@ inline std::string get(mrb_state *mrb, Key key, mrb_value hash) {
if (!seg.is_var) { if (!seg.is_var) {
result += seg.text; result += seg.text;
} else { } else {
mrb_sym sym = get_var_sym(mrb, seg.text); auto param_it = params.find(seg.text);
if (mrb_nil_p(hash) || mrb_nil_p(mrb_hash_get(mrb, hash, mrb_symbol_value(sym)))) { if (param_it == params.end()) {
result += "%{" + seg.text + "}"; result += "%{" + seg.text + "}";
continue; continue;
} }
mrb_value val = mrb_hash_get(mrb, hash, mrb_symbol_value(sym)); result += param_it->second;
if (mrb_nil_p(val))
continue;
result += mrb_str_to_cstr(mrb, val);
} }
} }
+5 -4
View File
@@ -1,4 +1,4 @@
App.run 720, 480, "Button Test", fps: 60 App.run 720, 480, "Button Test", fps: 240
main_scene = Scene.new 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_animation = Animation.new [bg_image1, bg_image2], fps: 5
bg_image1_a = Animation.from(bg_image1)
App.language = :en App.language = :en
App.localize(:en, :btn_text, "Click %{name}!") App.localize(:en, :btn_text, "Click %{name}!")
@@ -38,10 +40,10 @@ rect.click do
0x00FF00 : 0x00FF00 :
0xFF0000 0xFF0000
pp text.params[:name]
text.params[:name] = text.params[:name] == "Me" ? text.params[:name] = text.params[:name] == "Me" ?
"You" : "You" :
"Me" "Me"
text.params!
end end
App.map_key :mouse_left, :action_name App.map_key :mouse_left, :action_name
@@ -54,9 +56,8 @@ App.update do |delta_time|
c_ += delta_time c_ += delta_time
c_c += 1 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 # 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) el = App.scene.elements_at(App.mouse_position)