Fix major incorrect usage of mrb class definitions causing segfaults

This commit is contained in:
2026-05-05 21:09:52 +01:00
parent d362bbc114
commit 0da950caf9
9 changed files with 393 additions and 152 deletions
+12 -35
View File
@@ -8,7 +8,6 @@
namespace app {
struct Element {
int reference_count = 0;
Vec2<float> position = {0, 0};
float rotation = 0;
Vec2<float> scale = {1, 1};
@@ -21,11 +20,8 @@ struct Element {
virtual Vec2<float> 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 *> element_pool;
inline Pool<Element> element_pool;
struct Scene {
bool unused = false;
std::vector<int> 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 *> scene_pool;
inline Pool<Scene> 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;
}
+215 -49
View File
@@ -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<int> 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<float> 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));
+23 -10
View File
@@ -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)
+16
View File
@@ -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 <SDL3/SDL.h>
#include <SDL3/SDL_events.h>
+55 -22
View File
@@ -152,38 +152,71 @@ struct Rect {
}
};
template <typename T>
template <typename T = void *>
struct Pool {
T &operator[](int index) {
return items[index];
}
const std::vector<T> &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<T *> &all() {
static std::vector<T *> 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<T> items;
struct Slot {
T *ptr = nullptr;
int refcount = 0;
bool alive = false;
};
std::vector<Slot> slots;
std::vector<int> free_indices;
};
+11 -1
View File
@@ -14,6 +14,11 @@ struct Font {
bool pixel_art;
};
struct Text {
SDL_Texture *texture;
Vec2<float> size;
};
struct Event {
enum Type {
NONE,
@@ -57,8 +62,11 @@ struct Animation {
std::vector<int> frames;
float frame_rate;
bool loop;
int reference_count = 0;
};
inline Pool<Animation> 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<float> 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> font_pool;
// Text pool
Pool<SDL_Texture *> text_pool;
Pool<Text> text_pool;
struct RenderInstruction {
enum Type {