cocos2dx自带的lua-binding没有处理好std::vector的情况,修改过程如下:
cocos2d-x/tools/bindings-generator/generator.py
通过命令行调用这个py脚本,vscode调试配置
{ "version": "0.2.0", "configurations": [ { "name": "Python: generator", "type": "python", "request": "launch", "program": "E:/project/engineTools/frameworks/cocos2d-x/tools/bindings-generator/generator.py", "console": "integratedTerminal", "justMyCode": true, "cwd": "E:/project/engineTools/frameworks/cocos2d-x/tools/tolua", "args": [ "e:/project/engineTools/frameworks/cocos2d-x/tools/tolua/cocos2dx_curve.ini", "-s", "cocos2dx_curve", "-t", "lua", "-o", "e:/project/engineTools/frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto", "-n", "lua_cocos2dx_curve_auto" ] } ] } 复制代码
需要注意args,和ini的配置有关系
执行逻辑流程:
- 解析args参数
- 读取userconf.ini配置,这个配置是 tolua里面自动生成的
- 根据target查找对应的模板代码
- 循环依次根据sectoins生成对应的bunding代码
比较核心的2段代码
# 在Generator的构造函数里面,会解析ini配置里面的参数 generator = Generator(gen_opts) # 开始正式生成代码 generator.generate_code() 复制代码
- 读取lua/conversions.yaml配置文件
definitions: # the names of the functions - we use this to generate the code and to register the functions in # the javascript class ifunction: "lua_${generator.prefix}_${class_name}_${func_name}" sfunction: "lua_${generator.prefix}_${class_name}_${func_name}" constructor: "lua_${generator.prefix}_${class_name}_constructor" conversions: # some times you want to use a special native type when converting from spidermonkey to native # the most common case would be from JS-boolean to bool. Using "bool" will fail here since we # pass the address to the conversion method, and a JSBool is defined as an integer in spidermonkey native_types: float: "double" ns_map: "cocos2d::experimental::ui::": "ccexp." to_native: # lua to native int: "ok &= luaval_to_int32(tolua_S, ${arg_idx},(int *)&${out_value}, \"${lua_namespaced_class_name}:${func_name}\")" from_native: # native to lua int: "tolua_pushnumber(tolua_S,(lua_Number)${in_value})" 复制代码
- 确定*.hpp, *.cpp, _api.lua文件路径
- layout_foot.(h,c),layout_head.(h,c)这里面是cheetah模板文件
layout_head.h
\#include "base/ccConfig.h" #if $macro_judgement $macro_judgement #end if \#ifndef __${prefix}_h__ \#define __${prefix}_h__ #if $hpp_headers #for header in $hpp_headers \#include "${header}" #end for #end if \#ifdef __cplusplus extern "C" { \#endif \#include "tolua++.h" \#ifdef __cplusplus } \#endif int register_all_${prefix}(lua_State* tolua_S); 复制代码
模板结果
#include "base/ccConfig.h" #ifndef __cocos2dx_curve_h__ #define __cocos2dx_curve_h__ #ifdef __cplusplus extern "C" { #endif #include "tolua++.h" #ifdef __cplusplus } #endif int register_all_cocos2dx_curve(lua_State* tolua_S); #endif // __cocos2dx_curve_h__ 复制代码
以layout_foot.h为例
\#endif // __${prefix}_h__ #if $macro_judgement \#endif //$macro_judgement #end if 复制代码
主要的lua-binding逻辑代码
def generate_code(self): # ... 检查语法是否正确等相关工作 self._parse_headers() #解析c++的头文件 def _parse_headers(self): for header in self.headers: self._deep_iterate(tu.cursor) # 依次迭代检测语法树 def _deep_iterate(self, cursor, depth=0): if is_targeted_class and self.in_listed_classes(cursor.displayname): if not self.generated_classes.has_key(cursor.displayname): nclass = NativeClass(cursor, self) nclass.generate_code() # 真正开始生成binding代码的入口 class NativeClass(object): def generate_code(self): # ... 前后的逻辑都是准备工作,创建模板引擎 for m in self.methods_clean(): m['impl'].generate_code(self) for m in self.static_methods_clean(): m['impl'].generate_code(self) if self.generator.script_type == "lua": for m in self.override_methods_clean(): m['impl'].generate_code(self, is_override = True) for m in self.public_fields: if self.generator.should_bind_field(self.class_name, m.name): m.generate_code(self) class NativeFunction(object): # 生成具体的function def generate_code(self): # 到这里就需要看具体对应的模板文件逻辑了 Template(file=os.path.join(gen.target, "templates", "sfunction.c"), searchList=[current_class, self]) 复制代码
在sfunction.c模板文件中有调用
${arg.to_native({})} 复制代码
to_native
又回到了Python的脚本里面,这里就需要关注下c++头文件,举个例子:
class Action :public ActionInterval { public: static Action* create(float duration, std::vector<Vec2> points); }; 复制代码
def to_native(self, convert_opts): #344 keys = [] # self.name为函数参数的类型 # 比如Action的create函数参数分别为float、cocos2d::Vec2(注意这里是带namespace的) keys.append(self.name) if self.is_object: # 这里判断 if not NativeType.dict_has_key_re(to_native_dict, keys): keys.append("object") 复制代码
def dict_has_key_re(dict, real_key_list): for real_key in real_key_list: for (k, v) in dict.items(): if k.startswith('@'): k = k[1:] # @开头的,视为正则表达式,@之后的内容为正则 match = re.match("^" + k + "$", real_key) if match: return True else: if k == real_key: return True return False 复制代码
比如Action的create方法,最终正则计算为
reg.match("^vector<cocos2d::Vec2.*>$", "vector<cocos2d::Vec2, std::allocator<cocos2d::Vec2> >") 复制代码
conversions.yml需要这么写
conversions: to_native: "@vector<cocos2d::Vec2.*>":"这个是能够匹配上的", "@vector<Vec2.*>":"这个是无法匹配上的,因为没有写namespace", 复制代码
value内容的书写,也需要遵守cheetah的语法,才能被模板引擎正确的替换。
最终结果:
conversions: to_native: "@vector<cocos2d::Vec2.*>": "ok &= luaval_to_std_vector_vec2(tolua_S, ${arg_idx}, &${out_value}, \"${lua_namespaced_class_name}:${func_name}\")" 复制代码
hpp里面目录的问题
targets/lua/templates/layout_head.c
\#include "scripting/lua-bindings/auto/${out_file}.hpp" #if $macro_judgement $macro_judgement #end if #for header in $headers #set relative = os.path.relpath(header, $search_path) #if not '..' in relative \#include "${relative.replace(os.path.sep, '/')}" #else \#include "${os.path.basename(header)}" #end if #end for \#include "scripting/lua-bindings/manual/tolua_fix.h" \#include "scripting/lua-bindings/manual/LuaBasicConversions.h" #if $cpp_headers #for header in $cpp_headers \#include "${header}" #end for #end if 复制代码
中间的目录跟$headers
,$search_path
有关系,
'search_path': os.path.abspath(os.path.join(userconfig.get('DEFAULT', 'cocosdir'), 'cocos')), 复制代码
- $search_path: frameworks\cocos2d-x\cocos
- $headers:对应init里面的headers
os.path.relpath
的逻辑,导致只有在相对于cocos目录时,才会显示带目录的include,否则只显示文件名,所以想要带上路径的文件名,在不改动Python的前提下,只能放到cocos目录了