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的詳細定義,敬請期待下一篇。
如果本文對你的開發有所幫助,并且你手頭恰好有零錢。
不如打賞我一杯咖啡,鼓勵我繼續分享優秀的文章。
