β

cocos2d-x脚本引擎(JavaScript、Lua等等)的大统一(方案概述)

C++博客-首页原创精华区 478 阅读
cocos2d-x支持多种脚本引擎的绑定,例如支持lua(通过lua或luajit)、javascript(通过SpiderMonkey脚本引擎),分别对应libluacocos2d和libjscocos2d两个工程,每个工程里分别对应大量的自动绑定和手动绑定代码。如果需要增加一些引擎功能需要绑定到脚本的话,两个工程都需要修改代码,非常不便于维护。假如希望使用其他脚本引擎的话(例如google红红火火的V8,或者ms的chakra),那得多开几个工程,每个工程都需要实现几乎一样,但是又不一样的代码。现在我提出一种思想,来解决这类问题。
现有的脚本引擎多如牛毛,而不同企业间可能有不同的技术积累,希望选择不一样的脚本引擎,有的python蟒蛇派,有的是lua派,有的是ruby派,还有JavaScript、lua等等等等,所以,JavaScript和Lua不应该成为二选一。
而这些脚本引擎之间,有很多的共同点,例如都是弱类型语言,都是支持那么几种简单类型,都是使用GC机制来回收等等。
脚本引擎统一化,是把多个脚本引擎(lua、JavaScriptCore、chakra、v8、spidermonkey等等)通过同一套抽象接口进行封装,引擎的脚本绑定代码都通过抽象接口来编写。通过选择编译不同的实现层来实现脚本引擎间的切换,而不是每个脚本引擎都要写不一样的脚本绑定代码,这样能大大简化脚本绑定层的维护成本,并能保证所有脚本引擎的接口的绝对一致性,并让用户轻松选择使用哪个脚本引擎,而不限定于必须使用官方选择的引擎。
脚本引擎抽象层API的定义非常关键,概括起来我认为他必须符合以下要求:
1、抽象层的接口必须在各个脚本引擎之间都可以实现,例如:“创建字符串”这个接口,是任意脚本引擎都能实现。
2、抽象层在绑定脚本的过程中是足够用的,例如:我们需要导出一个C++的类,还需要导出类里的函数,还有特殊的结构体,甚至包含lambda表达式,这些都需要考虑进去抽象层定义的需求里。
3、抽象层还必须足够的薄,薄到运行时根本感觉不到他的存在。需要使用“宏”或者inline函数的方式来给这个抽象层减肥,坚决不使用C++类的方式来加厚他。
4、抽象层在使用的过程中必须足够简单,简单到就好像是一个单独的脚本引擎API一样,这些API看上去是大统一的,并且是脚本语言无关的。
在定义抽象层的过程中,我参考了很多别人的方案,最终,我使用 了这么个方案:
1、使用C inline函数来定义API接口函数的声明,各个引擎的实现部分分别实现这些函数
2、定义一些基本类型,基本类型有各个脚本引擎来最终定义,但名字和意义都是统一的,初步定义了这些类型,每个类型在不同脚本引擎中对应的类型分别如下表:
统一类型
描述
Lua
JavaScript
JavaScriptCore
V8
chakra
USValue
表示脚本中的任意类型
任意类型

Object
JSValueRef
v8::Local<v8::Value>
JsValueRef
USObject
脚本对象,对象可以由key、value组成,可以拥有继承结构
table
Object
JSObjectRef
v8::Local<v8::Object>
JsValueRef
USFunction
脚本函数
function
Function
JSObjectRef
v8::Local<v8::Function>
JsValueRef
USArray
数组类型,下标从0开始
table
Array
JSObjectRef
v8::Local<v8::Array>
JsValueRef
USMap
键-值配对的map类型
table
Map
JSObjectRef
v8::Local<v8::Object>
JsValueRef
USSet
值作为键也作为值得列表,值不能重复
table
Set
JSObjectRef
v8::Local<v8::Object>
JsValueRef
USNumber
数值类型
number
Number
JSValueRef
v8::Local<v8::Number>
JsValueRef
USBoolean
bool类型
boolean
Boolean
JSValueRef
v8::Local<v8::Boolean>
JsValueRef
USString
字符串类型
string
String
JSValueRef
v8::Local<v8::String>
JsValueRef
USBuffer
内存块缓存对象
string
Int8Array
JSValueRef
v8::Local<v8::Int8Array>
JsValueRef
USConstructor
对象实例的构造器,用来导出C++类
table
Object
JSValueRef
v8::Local<v8::Object>
JsValueRef
3、定义一批API,用以对以上定义的基本类型进行创建、调用、修改等操作,例如创建的过程API定义成这种形式:

1  // 创建Null值
2     inline USValue createUSNull();
3  // 创建Undefined值
4     inline USValue createUSUndefined();
5  // 创建普通的对象
6     inline USObject createUSObject();
7  // 创建数组
8     inline USArray createUSArray(int length = 0);
9  // 创建Map
10     inline USMap createUSMap();
11  // 创建Set
12     inline USSet createUSSet();
13  // 创建字符串
14     inline USString createUSString(const char *str, int length = -1);
15  // 创建Buffer,将会拷贝数据到Buffer中,脚本引擎负责销毁
16     inline USBuffer createUSBuffer(const char *buffer, size_t size);
17  // 创建一个脚本函数,函数调用时会回调到callback,并带上data
18     USFunction createUSFunction(USFunctionCallback callback, void *data = nullptr, const char *name = nullptr);
19  // 创建数字
20     inline USNumber createUSNumber(double number);
21  // 创建bool
22     inline USBoolean createUSBoolean(bool value);
23
24  // 创建对象构造器
25     USConstructor USClassCreateConstructor(const USClass &cls);
抽象层定义好后,需要经过大量的努力,才能在各个脚本引擎间的最终实现。当最终实现完毕后,就可以下一步工作:
1、把cocos2d-x对于自动绑定代码的template类进行修改,修改成使用统一脚本引擎的API
2、把cocos2d-x对于手动绑定的代码如法炮制
3、使用不同的引擎实现来编译
经过这样如法炮制之后,最终cocos2d-x只剩下一套脚本绑定的工程,而通过选择不同的底层脚本引擎,却可以编译出完全不一样的脚本引擎版本。
经过cocos2d-x github社区的努力,最终应该会出现不同的fork,如python、ruby等等各种语言出现各种绑定版本,而这种绑定版本的出现,只需实现抽象层API的基本API即可。
至此cocos2d-x的脚本引擎统一即可完成大业。
但,事情还没完,我在下一篇中,将会讲到抽象API的详细定义,敬请期待下一篇。


mybios 2015-11-20 15:03 发表评论

发表评论