4

I'm writing an Ruby extension for a physics engine. This physics engine has bodies that are linked to a world, so my Ruby objects are World and Body. A body is constructed (in C++) with world->CreateBody and destroyed with world->DestroyBody.

The problem is that the Ruby GC is destroying the world before the bodies. So, when the GC destroy the bodies, the world no longer exists and I get a segmentation fault. I know that I need to mark something somewhere for the GC (using rb_gc_mark), but I don't know where.


The World class is pretty standard, it looks like this:

extern "C" void world_free(void *w)
{
    static_cast<World*>(w)->~World();
    ruby_xfree(w);
}

extern "C" void world_mark(void *w)
{
    // ???
}

extern "C" VALUE world_alloc(VALUE klass)
{
    return Data_Wrap_Struct(klass, world_mark, world_free, ruby_xmalloc(sizeof(World)));
}

extern "C" VALUE world_initialize(VALUE self)
{
    World* w;
    Data_Get_Struct(self, World, w);
    new (w) World();
    return self;
}

The Body class is a little different, since it needs to be created from a World object (I can't simply new it). So it looks like this:

extern "C" void body_free(void* b)
{
    Body* body = static_cast<Body*>(b);
    World* world = body->GetWorld();
    world->DestroyBody(body);
}

extern "C" void body_mark(void* b)
{
    // ???
}

extern "C" VALUE body_alloc(VALUE klass)
{
    return Data_Wrap_Struct(klass, body_mark, body_free, 0);
}

extern "C" VALUE static_obj_initialize(VALUE self, VALUE world)
{
    Body* b;
    World* w;

    Data_Get_Struct(self, Body, b);
    Data_Get_Struct(world, World, w);

    b = w->CreateBody();
    DATA_PTR(self) = b;

    return self;
}

So my questions are:

  1. Which of the two objects should I mark on the GC?
  2. How do I do it? Do I simply mark with rb_gc_mark, or should I do it only under some conditions? Which ones?
  3. What should I mark? The mark functions receive only a naked pointer to my struct, but the rb_gc_mark function expects a VALUE.
André Wagner
  • 1,330
  • 15
  • 26
  • 2
    I don’t think `rb_gc_mark` will help here — if the entire world-body structure becomes unreachable then there is no way (as far as I know) to control the order that the free functions are called. The problem is that the function to free a `Body` depends on the existence of the corresponding `World`. It looks like the C++ API of the library doesn’t map cleanly onto a Ruby API where `Worlds` and `Body`s are independent objects — you may need to rethink how the objects are created and handled, and their lifecycles. – matt Jul 31 '14 at 21:23
  • I see. The problem is that the objects are in a external library - that is, I have no control over it. – André Wagner Aug 01 '14 at 11:14

1 Answers1

1

You should be able to register the VALUE of World as busy tied to the lifecycle of all instances of Body tied to it.

This means that when they are created, you'll want to do something like this:

extern "C" VALUE static_obj_initialize(VALUE self, VALUE world)
{
    Body* b;
    World* w;

    Data_Get_Struct(self, Body, b);
    Data_Get_Struct(world, World, w);

    b = w->CreateBody();
    DATA_PTR(self) = b;
    rb_gc_register_address(&world);

    return self;
}

The you'd want to unregister when the Body is destroyed:

extern "C" void body_free(void* b)
{
    Body* body = static_cast<Body*>(b);
    World* world = body->GetWorld();
    world->DestroyBody(body);
    rb_gc_unregister_address(&world); //almost right
}

This isn't quite accurate, however, because rb_gc_unregister_address() wants a VALUE *. You should be able to write a simple wrapper (or use std::pair<>) that'll carry the World VALUE.

Caveat: I haven't tested any of the above code, but it should be directionally correct.

benschumacher
  • 162
  • 2
  • 11