I'm writing a C extension in Ruby, and I'm running into a bit of a problem. I have a couple of C structs, here's a simplified version:
typedef struct
{
int score;
} Player;
typedef struct
{
int numPlayers;
Player *players;
} Game;
And I have these very nicely wrapped up in a C extension. I've set up methods for each struct, and here's the player ones since that's what I'm interested in:
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 fine. 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 computes a new array each time. So, when I add a player or append one, that array changes, but the underlying player array stays the same. I feel like I have to have two arrays, one in Ruby, one in C. Whenever I add some players to the C array, say in an initialize_game function, I would have to update the Ruby array, and whenever the Ruby array gets updated, there would have to be some sort of callback to update the C array. But, I'm not sure exactly how to do that.
g.players[0] = Player.new
is perhaps not a great approach anyway, because your caller is interfering too deep into theGame
object to do that - it needs to work with knowledge that Game should be encapsulating