當(dāng)creator遇上protobufjs—深入

一损合、 不修改源碼讓protobufjs適應(yīng)多平臺

我們上一篇《在cocos creator中使用protobufjs(一)》講解了通過修改源碼的方案,讓protobufjs能正常運(yùn)行在jsb環(huán)境上碍论。這個方案適合將protobufjs源碼直接放到項目中罐寨,而我們使用npm來管理三方庫的方式脱羡,這種方案就顯得不太優(yōu)雅捏膨。

1. 解決IS_NODE的檢查

之前源碼中已經(jīng)看到Util.IS_NODE是用來區(qū)分代碼是運(yùn)行在nodejs上還是瀏覽器上。我們可以模擬cocos-jsb為nodejs環(huán)境驯妄,我們看protobufjs是怎么來檢查環(huán)境的荷并。

Util.IS_NODE = !!(
    typeof process === 'object' && process+'' === '[object process]' && !process['browser']
);

上面這段代碼我們注意兩個地方:

  • !!:在一個變量或表達(dá)示前面使用“!!”的意思是將其值轉(zhuǎn)換為boolean值即true或false,這是js中常用的技術(shù)青扔,第一次見這種寫法的人可會犯暈源织。
  • process:process對象是nodejs的內(nèi)置進(jìn)程對象,在cocos-jsb上肯定是沒這貨微猖,那怎么辦呢谈息?
方案一:偽裝者
偽裝者---適配器模式

在require('protobufjs')之前我們自己定義一個process對象

if(cc.sys.isNative) {
    global.process = { 
        toString: () => '[object process]'
    }
}
...
require('protobufjs');

這種方案相當(dāng)于欺騙protobufjs我們是nodejs,這段代碼也解釋兩句:

  • global: global對象是js中很特殊的對象凛剥,全局的方法侠仇、屬性都集中在一個對象中。我們這里將process對象放到global上相當(dāng)于定義了全局變量犁珠。
  • toString方法:js中所有對象上都具有toString方法(除null\undefined外)逻炊,當(dāng)你在對象上使用字符串連接“+”操作時,其實是調(diào)用的對象的toString方法犁享。

這種方法可將coco-jsb化身為nodejs嗅骄,但感覺有點(diǎn)文縐縐的,我們再看看更直接的方法饼疙。

方案二:霸王硬上弓
霸王硬上弓---直接修改內(nèi)存
霸王硬上弓---直接修改內(nèi)存

在require('protobufjs')之后強(qiáng)制修改Util.IS_NODE的值

protobufjs.Util.IS_NODE = cc.sys.isNative;

這個方法簡單直接溺森,而且不怕他修改檢查方案,我覺得這個方法更好窑眯。

2. 解決fs.readFile/fs.readFileSync

...
if (Util.IS_NODE) {
    //cocos中那來的fs模塊呀屏积?
    var fs = require("fs");
    if (callback) {
        fs.readFile(path, function(err, data) {
            if (err)
                callback(null);
            else
                callback(""+data);
        });
    } else
        try {
            return fs.readFileSync(path);
        } catch (e) {
            return null;
        }
}
...

這里不能硬來了,硬來只能改源碼磅甩,使用偽裝的方法炊林,我們?nèi)ゾ帉懸粋€fs模塊

//fs.js
module.exports = {
    //同步讀取文件
    readFileSync(path) {
        //cocos-jsb提供有相同功能的函數(shù),就借用下它
        return jsb.fileUtils.getStringFromFile(path);
    }

    //異步讀取文件
    readFile(path, cb) {
        //cocos-jsb沒提供異步讀取文件的函數(shù)卷要,這里只能簡單執(zhí)行下回調(diào)傳回讀取內(nèi)容
        let str = jsb.fileUtils.getStringFromFile(path);
        cb(null, str); 
    },
}

我們這里是偷梁換柱渣聚,實現(xiàn)了一個fs模塊,這關(guān)算是過了僧叉。這里需要注意的是jsb.fileUtils對象奕枝,上面封裝有不少原生上的文件操作。


jsb.fileUtils對象上的方法

大多數(shù)方法一看名字就知道用法了瓶堕,這里就不再一一說明隘道。

3. 解決require("path")問題

源碼中有對path模塊的使用:

...
filename = require("path")['resolve'](filename);
...
fname = require("path")['join'](root, filename.file);
...

乍眼一看感覺這種寫法有點(diǎn)亂,其實它等同如下代碼:

let path = require("path");
filename = path.resolve(filename);
filename = path.join(root, filename.file);

這樣看就明白了,有個path模塊谭梗,調(diào)用了他的resolve和join方法忘晤,path偽裝再次登錄場:

//path.js
module.exports = {
    //獲取全路徑
    resolve: (subPath) => {
        //使用cc.url.raw實現(xiàn)獲取全路徑
        return cc.url.raw(`resources/${subPath}`);
    },
    // 方法使用平臺特定的分隔符把全部給定的path片段連接到一起
    join: () => {
        //使用cocos提供的cc.path.join實現(xiàn)
        return cc.path.join.apply(null, arguments); 
    }
}

問題終于被被解決了,估計好多人會覺得好麻煩激捏!我的demo中已經(jīng)實現(xiàn)了這些偽裝者文件设塔。 寫這么多其實主要是想讓大家了解的是javascript語言的靈活性,以及一種思路一種可能性远舅。如果覺得還是不能接受壹置,下面我再給大家介紹一種方案,預(yù)編譯proto文件表谊。

二、 使用預(yù)編譯方案

在靜態(tài)語言中使用protobuf都需要將proto文件編譯成目標(biāo)代碼盖喷,protobufjs模塊也為我們提供了pbjs命令行工具爆办。

1. pbjs工具介紹

pbjs命令幫助

上圖是pbjs命令工具的幫助,看起來參數(shù)不少课梳,但我們這里只需要很簡單的使用距辆,生成json格式或js格式。

2. 將proto編譯為json

pbjs xxx.proto > xxx.json

無需任何選項暮刃,直接輸入文件名跨算,將輸出json格式的proto文件。
我們來看下如何使用:

let protobuf = require('protobufjs')
let builder = new protobuf.Builder();
protobuf.loadJsonFile('xxx.json', builder);
protobuf.loadJsonFile('yyy.json', builder);
let PB = builder.build('xxx.yyy.zzz');

其實使用json格式與使用proto格式?jīng)]什么大的差別椭懊。讀過源碼的話知道诸蚕,protobufjs庫加載proto文件的順序大致如下:

  1. 加載proto文件
  1. 將獲取的proto字符串,解析為json對象
  2. build操作將json對象轉(zhuǎn)換為proto對象

使用預(yù)編譯json加載相當(dāng)于省略了第二步氧猬,直接加載json文件轉(zhuǎn)換proto對象背犯。
當(dāng)proto文件比較多的時候,使用json加載可以提高一些效率盅抚。

3. 將proto編譯為js

pbjs -t commonjs xxx.proto > xxx.js

使用pbjs提供的-t參數(shù)將proto文件編譯為目標(biāo)格式漠魏,這里我們指定的commonjs,后面緊跟proto文件名妄均。

//-----------------------------proto文件內(nèi)容-----------------------------------
syntax = "proto3";
package grace.proto.msg;

message Player {
    uint32  id = 1;         //唯一ID  首次登錄時設(shè)置為0柱锹,由服務(wù)器分配
    string  name = 2;       //顯示名字
    uint64  enterTime = 3;  //登錄時間
}
//-----------------------------編譯后的js文件內(nèi)容-------------------------------
module.exports = require("protobufjs").newBuilder({})['import']({
    "package": "grace.proto.msg",
    "syntax": "proto2",
    "messages": [
        {
            "name": "Player",
            "syntax": "proto3",
            "fields": [
                {
                    "rule": "optional",
                    "type": "uint32",
                    "name": "id",
                    "id": 1
                },
                {
                    "rule": "optional",
                    "type": "string",
                    "name": "name",
                    "id": 2
                },
                {
                    "rule": "optional",
                    "type": "uint64",
                    "name": "enterTime",
                    "id": 3
                }
            ]
        }
    ],
    "isNamespace": true
}).build();

大致一看編譯后的js文件,其實與使用proto文件丰包、json文件加載沒什么本質(zhì)的區(qū)別禁熏,簡單分析下面代碼:

module.exports = require("protobufjs").newBuilder({})['import']({
  //proto內(nèi)容的json格式
  ...
}).build();

1.require("protobufjs")導(dǎo)入protobufjs模塊,
2.newBuilder({}) 實例化一個builder對象
3.'import' 調(diào)用builder實例上的import方法導(dǎo)入一段json
4.build() 調(diào)用builder實例build方法邑彪,生成proto對象
5.module.exports 導(dǎo)出build()后的對象
使用預(yù)編譯js的方式不需要加載文件匹层,proto直接編寫在js文件中,當(dāng)proto文件較多時可以提高性能。

三升筏、 protobuf愛你不容易


我在使用protobuf的過程也不是一帆風(fēng)順撑柔,只能說protobuf愛你不容易!

1. 第一個項目

在最初的項目中您访,使用的是直接加載proto文件铅忿,當(dāng)時也沒想過使用預(yù)編譯的方式。項目中有接近上百個proto文件灵汪,proto文件由服務(wù)端程序定義的檀训,粒度非常小,幾個message就是一個proto文件享言。開發(fā)期間覺得沒什么問題峻凫,后來發(fā)布時,發(fā)現(xiàn)加載比較慢览露,性能差點(diǎn)的手機(jī)會特別明顯荧琼,因此還為加載proto文件的整個過程做了一個進(jìn)度條。

2. 卡牌項目

之后的一個卡牌項目中差牛,我們吸取了之前的經(jīng)驗命锄,與服務(wù)端程序討論定義proto文件時將同類數(shù)據(jù)結(jié)構(gòu)盡量定在一個文件中,不要太過分散偏化,任然使用直接加載proto文件的方式脐恩。在這項目中雖然protobuf的數(shù)據(jù)結(jié)構(gòu)更多,更復(fù)雜侦讨,但文件數(shù)量較少加載過程中沒有太大影響驶冒。

3. SLG項目

后來在一個SLG項目里我們?nèi)稳皇褂弥苯蛹虞dproto文件,但SLG項目的復(fù)雜度比之前的卡牌上升了好幾個數(shù)量級韵卤,protobuf文件個數(shù)只怎、數(shù)據(jù)結(jié)構(gòu)的規(guī)模都翻了幾倍,加載proto的加載過程在低配置手機(jī)上顯的非常慢怜俐,又只好為proto的加載過程制作進(jìn)度條身堡。

4. 小結(jié)

至此開始我才開始意識到直接加載大量proto文件的缺陷,在細(xì)讀protobufjs庫的文檔之后開始使用在項目中嘗試使用預(yù)編譯的方式拍鲤。

預(yù)編譯js方式解決了文件加載贴谎,但增加代碼編譯時間,在creator中可以將編譯的proto文件設(shè)置為插件季稳,不參與編譯擅这,但文件多了也是很麻煩。

預(yù)編譯json方式不會增加編譯時間景鼠,減少了proto到j(luò)son的轉(zhuǎn)換時間仲翎,但文件io操作任然是最大的瓶頸痹扇。

4. 覺知開發(fā)中的痛點(diǎn)

在protobuf的使用上,除了proto加載方案的選擇外溯香,還存在不少其它問題鲫构。

有項目使用json做協(xié)議,無需解碼玫坛,客戶端處理服務(wù)器響應(yīng)邏輯時比較方便结笨。但protobuf必須做解碼后才能讀取數(shù)據(jù)結(jié)構(gòu),proto對象的new湿镀、decode代碼充斥著客戶端項目炕吸。

在javascript項目使用protobuf還有一個痛點(diǎn)就是IDE無法很好支持proto對象的代碼補(bǔ)全,需要在代碼與proto原文件中來回切換勉痴,不時出現(xiàn)單詞拼寫錯誤等問題赫模。

下一次我們將繼續(xù)探索在項目中如何相對高效使用protobuf。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蒸矛,一起剝皮案震驚了整個濱河市瀑罗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌莉钙,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筛谚,死亡現(xiàn)場離奇詭異磁玉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)驾讲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門蚊伞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吮铭,你說我怎么就攤上這事时迫。” “怎么了谓晌?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵掠拳,是天一觀的道長。 經(jīng)常有香客問我纸肉,道長溺欧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任柏肪,我火速辦了婚禮姐刁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘烦味。我一直安慰自己聂使,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柏靶,像睡著了一般弃理。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宿礁,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天案铺,我揣著相機(jī)與錄音,去河邊找鬼梆靖。 笑死控汉,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的返吻。 我是一名探鬼主播姑子,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼测僵!你這毒婦竟也來了街佑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤捍靠,失蹤者是張志新(化名)和其女友劉穎沐旨,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榨婆,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡磁携,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了良风。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谊迄。...
    茶點(diǎn)故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖烟央,靈堂內(nèi)的尸體忽然破棺而出统诺,到底是詐尸還是另有隱情,我是刑警寧澤疑俭,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布粮呢,位于F島的核電站,受9級特大地震影響钞艇,放射性物質(zhì)發(fā)生泄漏鬼贱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一香璃、第九天 我趴在偏房一處隱蔽的房頂上張望这难。 院中可真熱鬧,春花似錦葡秒、人聲如沸姻乓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹋岩。三九已至赖草,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剪个,已是汗流浹背秧骑。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扣囊,地道東北人乎折。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像侵歇,于是被迫代替她去往敵國和親骂澄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容