Saving C array and Ruby array using extension?
I am writing a C extension in Ruby and I have a problem. I have a couple of C structures, here's a simplified version:
typedef struct
{
int score;
} Player;
typedef struct
{
int numPlayers;
Player *players;
} Game;
And I have these very well wrapped in a C extension. I set up methods for each struct, and here are the players since then what interests me:
static VALUE players(VALUE self)
{
Game *g;
Data_Get_Struct(self, Game, g);
VALUE players = rb_ary_new();
for (int i = 0; i<g->numPlayers; i++)
{
//cPlayer is the ruby class I defined in my Init method
VALUE player = Data_Wrap_Struct(cPlayer, NULL, NULL, &g->players[i]);
rb_ary_push(players, player);
}
return players;
}
static VALUE set_players(VALUE self, VALUE players)
{
Game *g;
Data_Get_Struct(self, Game, g);
free(g->players);
g->players = malloc(sizeof(Player)*RARRAY_LEN(players));
for (int i = 0; i<RARRAY_LEN(players); i++)
{
VALUE iValue = INT2NUM(i);
VALUE player = rb_ary_aref(1, &iValue, players);
Player *cPlayer;
Data_Struct_Get(player, Player, cPlayer);
memcpy(&g->players[i], cPlayer, sizeof(Player));
}
return Qnil;
}
And then, in my Ruby code, I could do something like this:
g = Game.new
g.players = [Player.new, Player.new, Player.new]
g.players.each {|player| player.score = 5}
This works great. However, I don't know how to do this:
g.players << Player.new
=> [<Player0>, <Player1>, <Player2>, <Player3>]
g.players
=> [<Player0>, <Player1>, <Player2>]
g.players[0] = Player.new
=> [<Player4>, <Player1>, <Player2>]
g.players
=> [<Player0>, <Player1>, <Player2>]
Obviously the problem is that when I access the array, it calculates a new array every time. So when I add a player or add one, this array changes, but the main array of players remains the same. I feel like I have two arrays, one in Ruby, one in C. When I add some players to the C array, say in the initialize_game function, I will have to update the Ruby array and whenever the Ruby array is updated, it should be what something is a callback to update the C array. But I don't know exactly how to do this.
source to share
You can keep the Ruby link VALUE
in your structure:
typedef struct
{
int numPlayers;
VALUE players;
} Game;
You will need to initialize it separately (I usually implement the function init
and call it after the alloc procedure).
Also, you have to tag the value with rb_gc_mark
on-demand - you need to fill those NULL
links in Data_Wrap_Struct
to play well with Ruby GC.
You really should already provide a destroy function, otherwise you will have memory leaks:
eg.
Data_Wrap_Struct( cGame, game_gc_mark, game_destroy, new_game );
game_gc_mark
should look like this:
void game_gc_mark( Game *g ) {
rb_gc_mark( g->players );
return;
}
game_destroy
should look like this:
void game_destroy( Game *g ) {
xfree( g );
return;
}
Note that there is no need to handle memory management for objects Array
or Player
within an array if you do - Ruby GC will handle this for you if you provide similar methods to destroy objects Player
.
The advantage of this is that all objects are accessible just like regular Ruby, while behind the scenes the data is stored in structures ready to be efficiently used for whatever method you want to implement in C. There is no to / from conversion. no creating new Ruby objects to keep track of relationships, just speed when you want it and have time to write C.
The downside is that you will need to protect the code from manipulating the array of players with Ruby code, otherwise you risk segfaults if you assume you have objects Player
, but in fact the error caused them to be some other class.
source to share