【发布时间】:2015-07-02 01:28:29
【问题描述】:
我正在用 Ruby 编写 C 扩展,但遇到了一些问题。我有几个 C 结构,这是一个简化版本:
typedef struct
{
int score;
} Player;
typedef struct
{
int numPlayers;
Player *players;
} Game;
我将这些很好地封装在一个 C 扩展中。我已经为每个结构设置了方法,这是玩家的,因为这是我感兴趣的:
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;
}
然后,在我的 Ruby 代码中,我可以这样做:
g = Game.new
g.players = [Player.new, Player.new, Player.new]
g.players.each {|player| player.score = 5}
这很好用。但是,我不知道该怎么做:
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>]
显然,问题是当我访问数组时,它每次都会计算一个新数组。因此,当我添加或追加一个播放器时,该数组会发生变化,但底层播放器数组保持不变。我觉得我必须有两个数组,一个在 Ruby 中,一个在 C 中。每当我将一些玩家添加到 C 数组中时,比如在 initialize_game 函数中,我必须更新 Ruby 数组,并且每当 Ruby 数组更新时,必须有某种回调来更新 C 数组。但是,我不确定该怎么做。
【问题讨论】:
-
我实际上只是灵光一现,意识到这种设计模式可能是真正的问题,我应该创建纯 Ruby 对象,而不是包装 C 结构。然后,在我的扩展中,我需要提高性能的方法应该从当前的 Ruby 数组创建新的结构,然后使用这些新创建的结构作为输入运行更快的 C 函数。似乎比我一直在做的更容易和更易于维护。其他人同意这是要走的路吗?
-
这取决于按需派生所需的结构并将结果写回 Ruby 对象的成本。您应该是“原生 Ruby,在 C 中转换/转换”或“原生 C,提供 Ruby 访问”。无论如何,调用
g.players[0] = Player.new可能不是一个好方法,因为你的调用者对Game对象的干扰太深,无法做到这一点 - 它需要知道 Game 应该封装