4.4.2 config和vconfig

config和vconfig都是C++中的class,vconfig是config为便于lua处理lua-config而新加的class。

C++、lua协同处理config,可分为以下步骤。

  1. (C++)向lua注册表保存vconfig元表。
  2. (C++)lua_pcall前,把vconfig作为参数压栈,并设置vconfig元表。
  3. (lua)执行cfg.__shallow_literal,触发元操作__index对应的impl_vconfig_get。
  4. (C++)impl_vconfig_get生成lua可使用的lua-config。
  5. (lua)使用lua-config

 

4.4.2.1 (C++)向lua注册表保存vconfig元表

std::string register_vconfig_metatable(lua_State *L)
{
  // 假设进入此函数时,栈中单元数是0。
  luaL_newmetatable(L, vconfigKey);
  // 此时stack是1。多出的栈单元就是新建的元表,该表已经以vconfigKey的指针为key保存在注册表。
  static luaL_Reg const callbacks[] {
    { "__gc",           &impl_vconfig_collect},
    { "__index",        &impl_vconfig_get},
    { "__len",          &impl_vconfig_size},
    { "__pairs",        &impl_vconfig_pairs},
    { "__ipairs",       &impl_vconfig_ipairs},
    { nullptr, nullptr }
  };
  luaL_setfuncs(L, callbacks, 0);
  // 此时stack是1。但元表已经新增了callbacks指示的7条记录。

  lua_pushstring(L, "wml object");
  // 此时stack是2。
  lua_setfield(L, -2, "__metatable");
  // 此时stack是1。至此元表新增加了一条"__metatable"="wml object"记录。
  // 为什么__metatable没有放在callbacks?——callbacks存的是函数,luaL_setfuncs把值放入栈中时用的是lua_pushcclosure。

  // Metatables for the iterator userdata

  // I don't bother setting __metatable because this
  // userdata is only ever stored in the iterator's
  // upvalues, so it's never visible to the user.
  luaL_newmetatable(L, vconfigpairsKey);
  lua_pushstring(L, "__gc");
  lua_pushcfunction(L, &impl_vconfig_pairs_collect);
  lua_rawset(L, -3);

  luaL_newmetatable(L, vconfigipairsKey);
  lua_pushstring(L, "__gc");
  lua_pushcfunction(L, &impl_vconfig_ipairs_collect);
  lua_rawset(L, -3);
}

register_vconfig_metatable后,栈会新增三个类型是table的栈单元,它们都已经保存在注册表。从顶到底依次是vconfigipairsKey、vconfigpairsKey、vconfigKey。

 

4.4.2.2 (C++)lua_pcall前,把vconfig作为参数压栈,并设置vconfig元表

void luaW_pushvconfig(lua_State *L, const vconfig& cfg)
{
  // 向栈中压入一个LUA_VUSERDATA类型数据,此栈单元的body部分就是以cfg为参数构造的vconfig。
  new(L) vconfig(cfg);
  // luaL_setmetatable依次执行两个操作。
  // 1)luaL_getmetatable。查询注册表,获取key=&vconfigKey记录的值,即第一步保存的vconfig元表,并压栈。
  // 2)lua_setmetatable。从栈出弹出vconfig表,并将其设置为-2索引上对象的元表,这个对象即body是“vconfig(cfg)”的userdata。
  luaL_setmetatable(L, vconfigKey);
  // 相比刚进入函数时的栈,至此多了一个LUA_VUSERDATA、且绑定了vconfig元表的栈单元。
}

经过luaW_pushvconfig,栈中多了一个LUA_VUSERDATA、且绑定了vconfig元表的栈单元。

这里详细解释下“new(L) vconfig(cfg)”。

void* operator new(size_t sz, lua_State* L)
{
  return lua_newuserdata(L, sz);
}

LUA_API void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue) {
  ...
}

rose重载了operator new函数,自个版本的new多了一个“lua_State*”参数,要用它调用lua_newuserdata。operator new的另一个参数sz则是要创建对象的字节数,此时就是sizeof(vconfig),sz细节参考C++语法。

  1. operator new(12, L)。lua_newuserdata会分配一块“udatamemoffset(1) + sizeof(vsize))”的UData内存块,并以LUA_VUSERDATA类型压栈。假设UData内存块地址是0x0e668380。
  2. 调用vconfig的构造函数创建vconfig对象,该对象位在的地址(this,以及返回值t)是0x0e668380 + 0x28(40) = 0x0e6683a8。数值40字节的计算方法是offsetof(Udata, uv) + (sizeof(UValue) * (1))。
  3. vconfig被分配了空间并构造完成,返回指向一个vconfig对象的指针this。

总之,执行“new(L) vconfig(cfg)”后,向栈中压入一个LUA_VUSERDATA类型数据,返回值指向这个数据的body部分,其类型是“vconfig*”。即满足以下语句。

vconfig* v = new(L) vconfig(cfg)

和operator new配对,存在个operator delete。

void operator delete(void*, lua_State *L)
{
  // Not sure if this is needed since it's a no-op
  // It's only called if a constructor throws while using the above operator new
  // By removing the userdata from the stack, this should ensure that Lua frees it
  lua_pop(L, 1);
}

但实际使用时应该不会调用到operator delete。栈是lua管理的,它要删除一个栈单元时,在释放内存前会先调用元方法__gc。对于vconfig,__gc对应的是impl_vconfig_collect。

static int impl_vconfig_collect(lua_State *L)
{
  vconfig *v = static_cast<vconfig *>(lua_touserdata(L, 1));
  v->vconfig::~vconfig();
  return 0;
}

它得到userdata的body,调用vconfig析构函数。之后由lua释放该userdata栈单元对应的内存。

回到operator delete,它的操作就是弹出栈顶单元,弹出时会执行删除,也就重复impl_vconfig_collect的逻辑。但它要求vconfig一定在栈顶单元,由于是lualib“透明”管理栈单元,于是基本不会直接调用“operator delete”

 

4.4.2.3 (lua)执行cfg.__shallow_literal,触发元操作__index对应的impl_vconfig_get

function helper.shallow_literal(cfg)
  if type(cfg) == "userdata" then
    return cfg.__shallow_literal
  else
    return cfg or {}
  end
end

cfg是lua中被调函数的第一个参数。为让lua可使用config,须先调用cfg.__shallow_literal。这条语用的作用是获取cfg中key="__shallow_literal"的value。由于cfg不存在key="__shallow_literal"的记录,于是触发元方法“__index”。

在C++,cfg已设置vconfig元表,触发元方法“__index”等于调用C函数impl_vconfig_get。

 

4.4.2.4 (C++)impl_vconfig_get生成lua可使用的lua-config

static int impl_vconfig_get(lua_State *L)
{
  vconfig *v = static_cast<vconfig *>(lua_touserdata(L, 1));
  ...

  bool shallow_literal = strcmp(m, "__shallow_literal") == 0;
  if (shallow_literal || strcmp(m, "__shallow_parsed") == 0)
  {
    // 生成的lua-config是一个表。
    lua_newtable(L);
    // 表分两部分,第一部分是attribute,记录式单元。
    BOOST_FOREACH (const config::attribute &a, v->get_config().attribute_range()) {
      // 假设attribute:rand = 1..150
      if (shallow_literal)
        luaW_pushscalar(L, a.second);
      else
        luaW_pushscalar(L, v->expand(a.first));
      lua_setfield(L, -2, a.first.c_str());
      // lua-config表添加了一条记录式单元:{rand = 1..150}。
    }
    // 表的第二部分是child,数组式单元。
    vconfig::all_children_iterator i = v->ordered_begin(), i_end = v->ordered_end();
    if (shallow_literal) {
      i.disable_insertion();
      i_end.disable_insertion();
    }
    for (int j = 1; i != i_end; ++i, ++j)
    {
      // 假设有下面这么个child。
      // [set_variable]
      //   ...
      // [/set_variable]
      // 每一个child是一个表,表有两个数组式单元。
      lua_createtable(L, 2, 0);
      lua_pushstring(L, i.get_key().c_str());
      lua_rawseti(L, -2, 1);
      // child内容是一个config,再次用luaW_pushvconfig压栈、设置元表。
      luaW_pushvconfig(L, i.get_child());
      lua_rawseti(L, -2, 2);
      lua_rawseti(L, -2, j);
      // lua-config表添加了一条数组式单元:{"set_variable", vconfig}
    }
    return 1;
  }
  ...
}

为直观,让再举一个config示例,对比它的WML格式和lua格式。

[event]
  name = prestart
  side = 2
  [objectives]
    ...
  [/objectives]
  [set_variable]
  [/set_variable]
[/event]

经过impl_vconfig_get后,生成下面的lua表。

{name = "prestart",
 side = 2,
 {"objectives", userdata(vconfig)},
 {"set_variable", userdata(vconfig)}
};

 

4.4.2.5 (lua)使用lua-config

local function handle_event_commands(cfg)
  local cmds = helper.shallow_literal(cfg)
  for i = 1,#cmds do
    local v = cmds[i]
    // #cmds只计算数组式单元,因而此处只会得到child。
    // 数组式的child是一个表,它有两个单元,cmd是child名称:set_variable,arg是child的内容。
    local cmd = v[1]
    local arg = v[2]
    // arg是child的内容,lua想要是访问它,又将触发__index,进而回调impl_vconfig_get。
    ...
  end
end

全部评论: 0

    写评论: