cocos2dx自带的lua-binding没有处理好std::vector<Vec2>的情况,修改过程如下:
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目录了
网友评论