Cocos Protocol Buffer メモ
この本はチュートリアルというよりProtobuf
の扱い基礎とCocos2d-JS v3.10
でProtobuf v2.5.0
を実装する解決案のメモっと言ったほうがいい。(専門語だらけで面倒くさいから通訳はしていませんです…)
内容について
Demo (cpp/java/python/js)
- Create Instance
- Serialize to String
- Serialize to File
- Parse from String
- Parse from File
- Dump GameInfo Instance
- Relection Dump Message
- Relection Generate Message
- Load Proto
- Encode / Decode / js
- Excel .xls to ProtoBuf .bin / Python
Protobuf-2.5.0
Generated libs
CocosProtobuf
Using Protobuf
with Cocos2d-JS
in JSBinding
– Send Data from C++
– Receive Data from C++
– Send Data from javascript
– Receive Data from javascript
本に出ていたデモやプロジェクトやツールなどは全てオープンソースにしました: GitHub.
Develop Enviroment Setup
Install Protocol Buffer
Protocol Buffers v3.0.0-beta-2
Released on Dec 31, 2015
libprotobuf.a, building for OSX, but linking in object file built for iOS, for architecture x86_64
Works perfect on all platform -- `iOS` `Android` `Browser` `Desktop`
Released on Feb 28, 2013
So plase notice that this memo and the demo project is based on Protocol Buffers v2.5.0 && Mac OS X.
Installation is very simple, we just need to follow the instruction in Readme.md
.
https://github.com/google/protobuf
https://github.com/google/protobuf/releases
Complie libprotobuf.a
root$ protoc --version libprotoc 2.5.0 # the generated .a file roor$ cd /usr/local/lib roor$ ls -al libprotobuf.a
Supported architecture
The generated libprotobuf.a
supports the following architectures:
root$ lipo -info libprotobuf.a Architectures in the fat file: libprotobuf.a are: armv7s armv7 i386 x86_64 arm64
-
x86_64
Mac OS X
-
i386
iPhone Simulator
-
armv7
iPhone3GS / iPhone4 / iPhone4S / iPad / iPad2 / iPad3 / iPad mini / iPod Touch 3G / iPod Touch4
-
armv7s
iPhone5 / iPhone5C / iPad4
-
arm64
iPhone5S / iPhone6 / iPhone6 Plus / iPhone6S / iPad Air / iPad mini2
Seperate the fat .a
lipo thin
lipo xxx.a -thin [armv7|armv7s|arm64|i386|x86_64] -output xxx_thin.a
For android device, we can just compiler from source — using .h
+ .cc
.
Xcode Setup
Add Source
-
Add folder
protobuf-2.5.0/src/google
toXcode
- Classes - protobuf - google - srouce code - lib - libprotobuf.a
-
Remove files
- All
.proto
- All
.cc
( we have.a
already )
- All
-
Remove folder
testing
testdata
-
Modify file
stubs/platform_macros.h
// ... #elif defined(__ppc__) #define GOOGLE_PROTOBUF_ARCH_PPC 1 #define GOOGLE_PROTOBUF_ARCH_32_BIT 1 // add start #elif defined(__aarch64__) #define GOOGLE_PROTOBUF_ARCH_ARM 1 #define GOOGLE_PROTOBUF_ARCH_64_BIT 1 // add end // ...
Add libprotobuf.a
Add the libprotobuf.a
to Build Parses ==> Link Binary With Libraries
Modify Search Path
-
Build Settings == > Search Paths ==> User Header Search Paths
eg : `$(SRCROOT)/../Classes/protobuf`
-
Build Settings == > Search Paths ==> Library Search Paths
eg :
$(SRCROOT)/../Classes/protobuf/lib
Message Proto
Define Protos
GameInfo.proto
package game.info; option java_package = "com.game.info"; option java_outer_classname = "GameInfoProtos"; import "GameEnum.proto"; // role info message RoleInfo { required string name = 1; required enumeration.RoleType type = 2; } // item info message ItemInfo { required string name = 1; optional int32 price = 2; } // game info message GameInfo { repeated RoleInfo roleInfo = 1; repeated ItemInfo itemInfo = 2; }
GameEnum.proto
package game.enumeration; option java_package = "com.game.enumeration"; option java_outer_classname = "GameEnumProtos"; // enumeration type enum RoleType { FIGHTER = 1; BOWMAN = 2; MAGICIAN = 3; }
Compiler .proto
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
and will generate :
-
c++
GameEnum.pb.h
GameEnum.pb.cc
GameInfo.pb.h
GameInfo.pb.cc
-
java
GameEnumProtos.java
GameInfoProtos.java
-
python
GameEnum_pb2.py
GameInfo_pb2.py
-
javascript
Only
.proto
needed
Serialize && Parse / C++
Folder structure
- CPP // proto generated files - GameEnum.pb.h - GameEnum.pb.cc - GameInfo.pb.h - GameInfo.pb.cc // .proto - proto - GameInfo.proto - GameEnum.proto // protobuf source code - google - protobuf - // ... // main test file - CPP_TEST.cpp
Create Instance
// Create a GameInfo object first game::info::GameInfo gameInfo; // add RoleInfo object to repeated field game::info::RoleInfo *pRoleInfo1 = gameInfo.add_roleinfo(); pRoleInfo1->set_name("name1"); pRoleInfo1->set_type(game::enumeration::FIGHTER); // add another RoleInfo object to repeated field game::info::RoleInfo *pRoleInfo2 = gameInfo.add_roleinfo(); pRoleInfo2->set_name("name2"); pRoleInfo2->set_type(game::enumeration::BOWMAN); // add ItemInfo object to repeated field game::info::ItemInfo *pItemInfo1 = gameInfo.add_iteminfo(); pItemInfo1->set_name("item1"); pItemInfo1->set_price(100); // add another ItemInfo object to repeated field game::info::ItemInfo *pItemInfo2 = gameInfo.add_iteminfo(); pItemInfo2->set_name("item2"); pItemInfo2->set_price(200);
Serialize to String
std::string gameInfoString; gameInfo.SerializeToString(&gameInfoString);
Serialize to File
ofstream outputFile("GameInfo.bin", std::ostream::binary); gameInfo.SerializeToOstream(&outputFile);
Parse from String
game::info::GameInfo gameInfo; gameInfo.ParseFromString(gameInfoString);
Parse from File
game::info::GameInfo gameInfo; ifstream is("GameInfo.bin", std::ifstream::binary); gameInfo.ParseFromIstream(&is); is.close();
Dump GameInfo Instance
// RoleInfo Array for (int i = 0; i < gameInfo.roleinfo_size(); i++) { // role info game::info::RoleInfo roleInfo = gameInfo.roleinfo(i); printf("roleInfo: \n%s\n", roleInfo.Utf8DebugString().c_str()); } // ItemInfo Array for (int i = 0; i < gameInfo.iteminfo_size(); i++) { // role info game::info::ItemInfo itemInfo = gameInfo.iteminfo(i); printf("itemInfo: \n%s\n", itemInfo.Utf8DebugString().c_str()); }
Relection Dump Message
void dumpMessageReflection(const Message* pMessageInfo) { // Descriptor const Descriptor* pDescriptor = pMessageInfo->GetDescriptor(); // Reflection const Reflection* pReflection = pMessageInfo->GetReflection(); // Fields for (int i = 0; i < pDescriptor->field_count(); i ++) { const FieldDescriptor* pMessageField = pDescriptor->field(i); int fieldSize = pMessageField->is_repeated() ? pReflection->FieldSize(*pMessageInfo, pMessageField) : 0; // Field switch(pMessageField->type()) { case FieldDescriptor::TYPE_INT64 : case FieldDescriptor::TYPE_UINT64 : case FieldDescriptor::TYPE_FIXED64 : case FieldDescriptor::TYPE_FIXED32 : case FieldDescriptor::TYPE_BOOL : case FieldDescriptor::TYPE_GROUP : case FieldDescriptor::TYPE_BYTES : case FieldDescriptor::TYPE_UINT32 : case FieldDescriptor::TYPE_SFIXED32 : case FieldDescriptor::TYPE_SFIXED64 : case FieldDescriptor::TYPE_SINT32 : case FieldDescriptor::TYPE_SINT64 : printf("FieldDescriptor type not handled !"); break; case FieldDescriptor::TYPE_INT32 : if (fieldSize == 0) { printf("Value: %d\n\n", pReflection->GetInt32(*pMessageInfo, pMessageField) ); } else { for (int j = 0; j < fieldSize; j ++) { printf("Value: %d\n\n", pReflection->GetRepeatedInt32( *pMessageInfo, pMessageField, j ) ); } } break; case FieldDescriptor::TYPE_FLOAT : if (fieldSize == 0) { printf("Value: %f\n\n", pReflection->GetFloat(*pMessageInfo, pMessageField) ); } else { for (int j = 0; j < fieldSize; j ++) { printf("Value: %f\n\n", pReflection->GetRepeatedFloat( *pMessageInfo, pMessageField, j ) ); } } break; case FieldDescriptor::TYPE_DOUBLE : if (fieldSize == 0) { printf("Value: %lf\n\n", pReflection->GetDouble(*pMessageInfo, pMessageField) ); } else { for (int j = 0; j < fieldSize; j ++) { printf("Value: %lf\n\n", pReflection->GetRepeatedDouble( *pMessageInfo, pMessageField, j ) ); } } break; case FieldDescriptor::TYPE_STRING : if (fieldSize == 0) { printf("Value: %s\n\n", pReflection->GetString( *pMessageInfo, pMessageField ).c_str() ); } else { for (int j = 0; j < fieldSize; j ++) { printf("Value: %s\n\n", pReflection->GetRepeatedString( *pMessageInfo, pMessageField, j ).c_str() ); } } break; case FieldDescriptor::TYPE_ENUM : if (fieldSize == 0) { printf("Value: %d\n\n", pReflection->GetEnum( *pMessageInfo, pMessageField )->number() ); } else { for (int j = 0; j < fieldSize; j ++) { printf("Value: %d\n\n", pReflection->GetRepeatedEnum( *pMessageInfo, pMessageField, j )->number() ); } } break; case FieldDescriptor::TYPE_MESSAGE : // recurse if (fieldSize == 0) { dumpMessageReflection( &pReflection->GetMessage(*pMessageInfo, pMessageField) ); } else { for (int j = 0; j < fieldSize; j ++) { dumpMessageReflection( &pReflection->GetRepeatedMessage( *pMessageInfo, pMessageField, j ) ); } } break; default: break; } } }
Relection Generate Message
compiler::DiskSourceTree sourceTree; sourceTree.MapPath("", "./proto"); compiler::Importer importer(&sourceTree, NULL); importer.Import("GameEnum.proto"); importer.Import("GameInfo.proto"); DynamicMessageFactory factory; const Descriptor* pDescriptorDynamic = NULL; const Reflection* pReflectionDynamic = NULL; const FieldDescriptor* pFieldDynamic = NULL; const EnumDescriptor* pEnumDescriptorDynamic = NULL; Message* pGameInfoDynamic = NULL; Message* pRoleInfoDynamic = NULL; Message* pItemInfoDynamic = NULL; // create a instance of GameInfo pDescriptorDynamic = importer.pool()->FindMessageTypeByName("game.info.GameInfo"); pGameInfoDynamic = factory.GetPrototype(pDescriptorDynamic)->New(); pReflectionDynamic = pGameInfoDynamic->GetReflection(); // get repeated roleinfo instance pFieldDynamic = pDescriptorDynamic->FindFieldByName("roleInfo"); pRoleInfoDynamic = pReflectionDynamic->AddMessage(pGameInfoDynamic, pFieldDynamic); // set string filed name pDescriptorDynamic = importer.pool()->FindMessageTypeByName("game.info.RoleInfo"); pEnumDescriptorDynamic = importer.pool()->FindEnumTypeByName("game.enumeration.RoleType"); pFieldDynamic = pDescriptorDynamic->FindFieldByName("name"); pRoleInfoDynamic->GetReflection()->SetString( pRoleInfoDynamic, pFieldDynamic, "role_name_dynamic1" ); // set enum filed type pFieldDynamic = pDescriptorDynamic->FindFieldByName("type"); pRoleInfoDynamic->GetReflection()->SetEnum( pRoleInfoDynamic, pFieldDynamic, pEnumDescriptorDynamic->FindValueByName("MAGICIAN") ); // get repeated iteminfo instance pDescriptorDynamic = importer.pool()->FindMessageTypeByName("game.info.GameInfo"); pFieldDynamic = pDescriptorDynamic->FindFieldByName("itemInfo"); pItemInfoDynamic = pReflectionDynamic->AddMessage(pGameInfoDynamic, pFieldDynamic); // set string filed name pDescriptorDynamic = importer.pool()->FindMessageTypeByName("game.info.ItemInfo"); pFieldDynamic = pDescriptorDynamic->FindFieldByName("name"); pItemInfoDynamic->GetReflection()->SetString( pItemInfoDynamic, pFieldDynamic, "item_name_dynamic1" ); // set int32 filed type pFieldDynamic = pDescriptorDynamic->FindFieldByName("price"); pItemInfoDynamic->GetReflection()->SetInt32( pItemInfoDynamic, pFieldDynamic, 999999 );
Compiler && Run
Comliper : g++ -c -I./ GameInfo.pb.cc g++ -c -I./ GameEnum.pb.cc g++ -c -I./ CPP_TEST.cpp Link : g++ -L./ -lprotobuf GameEnum.pb.o GameInfo.pb.o CPP_TEST.o -o main Compiler && Run : g++ -I./ -L./ -lprotobuf GameInfo.pb.cc GameEnum.pb.cc CPP_TEST.cpp -o main Run : ./main
Serialize && Parse / javascript
https://github.com/dcodeIO/protobuf.js
Load Proto
// cocos2d-js style require("bytebuffer.js"); require("protobuf.js"); var ProtoBuf = dcodeIO.ProtoBuf; // node.js style // var ProtoBuf = require("./dist/protobuf"); var GameInfoBuilder = ProtoBuf.newBuilder(); // load .proto ProtoBuf.loadProtoFile("../PROTO/GameInfo.proto", null, xappBuilder); ProtoBuf.loadProtoFile("../PROTO/GameEnum.proto", null, xappBuilder);
Create Instance
var Game = GameInfoBuilder.build("game"); // game var gameInfoOut = new Game.info.GameInfo(); // role var roleInfo1 = new Game.info.RoleInfo(); roleInfo1.name = "name1"; roleInfo1.type = Game.enumeration.RoleType.FIGHTER; gameInfoOut.roleInfo.push(roleInfo1); var roleInfo2 = new Game.info.RoleInfo(); roleInfo2.name = "name2"; roleInfo2.type = Game.enumeration.RoleType.BOWMAN; gameInfoOut.roleInfo.push(roleInfo2); // item var itemInfo1 = new Game.info.ItemInfo(); itemInfo1.name = "item1"; itemInfo1.price = 100; gameInfoOut.itemInfo.push(itemInfo1); var itemInfo2 = new Game.info.ItemInfo(); itemInfo2.name = "item2"; itemInfo2.price = 200; gameInfoOut.itemInfo.push(itemInfo2);
Encode / Decode
// encode var gameInfoData = gameInfoOut.encodeAB(); // decode var gameInfoIn = Game.info.GameInfo.decode(gameInfoData, "utf8"); // dump for (var i = 0; i < gameInfoIn.roleInfo.length; i ++) { var roleInfo = gameInfoIn.roleInfo[i]; console.log(/* ... */); } for (var i = 0; i < gameInfoIn.itemInfo.length; i ++) { var itemInfo = gameInfoIn.itemInfo[i]; console.log(/* ... */); }
Load from .bin
var gameInfo = new Game.info.GameInfo(); var content = ProtoBuf.Util.fetch("../BIN/RoleInfo.bin"); var gameInfo = Game.info.GameInfo.decode(content, "utf8"); for (var i = 0; i < gameInfo.roleInfo.length; i ++) { var roleInfo = gameInfo.roleInfo[i]; console.log(/* ... */); } content = ProtoBuf.Util.fetch("../BIN/ItemInfo.bin"); gameInfo = Game.info.GameInfo.decode(content, "utf8"); for (var i = 0; i < gameInfo.itemInfo.length; i ++) { var itemInfo = gameInfo.itemInfo[i]; console.log(/* ... */); }
Excel .xls
to ProtoBuf .bin
/ Python
Excel Format
excel : ItemInfo.xls
sheet name: ItemInfo
id | name | price |
---|---|---|
1 | item_name_1 | 111 |
2 | item_name_2 | 222 |
3 | item_name_3 | 333 |
excel : RoleInfo.xls
sheet name: RoleInfo
id | name | type |
---|---|---|
1 | role_name_1 | 1 |
2 | role_name_2 | 2 |
3 | role_name_3 | 3 |
Compiler .proto
# dir PB_DIR = './PROTO' PB_PY_DIR = './PYTHON' XLS_DIR = './XLS' BIN_DIR = './BIN' ''' eg: compile_protobuf(PB_DIR, PB_PY_DIR, 'a.proto', 'python') compile_protobuf(PB_DIR, PB_CPP_DIR, 'a.proto', 'cpp') compile_protobuf(PB_DIR, PB_JAVA_DIR, 'a.proto', 'java') ''' def compile_protobuf(src, dst, proto_file, language): if language == 'cpp': out_language = 'cpp_out' elif language == 'python': out_language = 'python_out' elif language == 'java': out_language = 'java_out' else: out_language = 'python_out' os.system('protoc -I=%s --%s=%s %s' % ( src, out_language, dst, src + '/' + proto_file) )
The output :
- PYTHON GameInfo_pb2.py GameEnum_pb2.py
Dynamic import
import inspect def dynamic_import(): import PYTHON.GameInfo_pb2 import_py = __import__('PYTHON.GameInfo_pb2') pb_attr = getattr(import_py, 'GameInfo_pb2') del import_py for name, obj in inspect.getmembers(PYTHON.GameInfo_pb2): if inspect.isclass(obj): import_pb_class(pb_attr, name) def import_pb_class(pb_attr, class_name): attr = getattr(pb_attr, class_name) setattr(sys.modules[__name__], class_name, attr) descriptor = make_descriptor_name(class_name) attr = getattr(pb_attr, descriptor) setattr(sys.modules[__name__], descriptor, attr) def make_descriptor_name(class_name): return '_' + class_name.upper()
Parse .xls
def parse_xls_files(): for worksheet_name, table in get_xls_files().items(): message_object = class_from_string(worksheet_name) if not message_object: continue fields = list_fields(make_descriptor_name(worksheet_name)) parse_table(worksheet_name, table, fields) def class_from_string(class_name): try: instance = getattr(sys.modules[__name__], class_name)() return instance except: return None
List fields
def list_fields(class_name): fields = [] for field in getattr(sys.modules[__name__], class_name).fields: fields.append([field.name, field.type, field.index]) return fields
Parse table
def parse_table(worksheet_name, table, fields): ## all data will be put into a GameInfo instance gameInfo = class_from_string('GameInfo') for ri in range(len(table)): # ... row = table[ri] message = None for field in fields: value_index = get_value_index(table[0], field[0]) # ... value = convert_pb_type(row[value_index][1], field[1]) # ... message = getattr( gameInfo, worksheet_name[0].lower() + worksheet_name[1:] ).add() setattr(message, field[0], value) # write to .bin # ... serializedObject = gameInfo.SerializeToString() pb_bin_file = "./bin/%s.bin" % worksheet_name pb_bin = open(pb_bin_file, "wb") pb_bin.write(serializedObject) pb_bin.close() def convert_pb_type(value, type): if type in [FieldDescriptor.TYPE_INT64, FieldDescriptor.TYPE_UINT64, FieldDescriptor.TYPE_INT32, FieldDescriptor.TYPE_ENUM]: return int(value) elif type in [FieldDescriptor.TYPE_DOUBLE, FieldDescriptor.TYPE_FLOAT]: return float(value) elif type in [FieldDescriptor.TYPE_STRING]: if isinstance(value, basestring): return value elif isinstance(value, float): return str(int(value)) else: return str(value) else: return None
Generated .bin
// ItemInfo.bin GameInfo repeated RoleInfo roleInfo => { RoleInfo1, RoleInfo2, RoleInfo3, ... } repeated ItemInfo itemInfo => None // RoleInfo.bin GameInfo repeated RoleInfo roleInfo => None repeated ItemInfo itemInfo => { ItemInfo1, ItemInfo2, ItemInfo3, ... }
.bin
security
.bin
can be decoded using protoc
command.
decode with protoc
command
import sys import subprocess def decode(message_name, proto_name, bin_name): process = subprocess.Popen( ['/usr/local/bin/protoc', '--decode', message_name, proto_name], stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE ) output = error = None try: f = open(bin_name, "rb") data = f.read() f.close() output, error = process.communicate(data) if error: print 'error: ' + error except OSError: pass finally: if process.poll() != 0: process.wait() return output print '------ result ------\n' print 'message_name: ' + sys.argv[1] print 'proto_name: ' + sys.argv[2] print 'bin_name: ' + sys.argv[3] print print decode(sys.argv[1], sys.argv[2], sys.argv[3]) print '------ ------ ------\n'
Test with ItemInfo.bin
root$ python decode.py game.info.GameInfo GameInfo.proto ItemInfo.bin ------ result ------ message_name: game.info.GameInfo proto_name: GameInfo.proto bin_name: ItemInfo.bin itemInfo { name: "item1" price: 100 } itemInfo { name: "item2" price: 101 } ...... ------ ------ ------
decode raw data with protoc
command
import sys import subprocess def decode_raw(data): process = subprocess.Popen( ['/usr/local/bin/protoc', '--decode_raw'], stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE ) output = error = None try: output, error = process.communicate(data) except OSError: pass finally: if process.poll() != 0: process.wait() return output f = open(sys.argv[1], "rb") data = f.read() print '------ data ------\n' print data print '------ ------ ------\n' print '------ result ------\n' print decode_raw(data) print '------ ------ ------\n' f.close()
Test with ItemInfo.bin
root$ python decode_raw.py ItemInfo.bin ------ data ------ item1d item2e item3f item4g item5h item6i item7j item8k item9l item10m ------ ------ ------ ------ result ------ 2 { 1: "item1" 2: 100 } 2 { 1: "item2" 2: 101 } 2 { 1: "item3" 2: 102 } ...... ------ ------ ------
decode raw data ( network data transfer )
JSBinding
Send from C++
JSObject* pGlobalObject = ScriptingCore::getInstance()->getGlobalObject(); JSContext* pGlobalContext = ScriptingCore::getInstance()->getGlobalContext(); // Create a GameInfo object first game::info::GameInfo gameInfo; // add RoleInfo object to repeated field game::info::RoleInfo *pRoleInfo1 = gameInfo.add_roleinfo(); pRoleInfo1->set_name("cpp_name1"); pRoleInfo1->set_type(game::enumeration::FIGHTER); // add another RoleInfo object to repeated field game::info::RoleInfo *pRoleInfo2 = gameInfo.add_roleinfo(); pRoleInfo2->set_name("cpp_name2"); pRoleInfo2->set_type(game::enumeration::BOWMAN); // add ItemInfo object to repeated field game::info::ItemInfo *pItemInfo1 = gameInfo.add_iteminfo(); pItemInfo1->set_name("cpp_item1"); pItemInfo1->set_price(100); // add another ItemInfo object to repeated field game::info::ItemInfo *pItemInfo2 = gameInfo.add_iteminfo(); pItemInfo2->set_name("cpp_item2"); pItemInfo2->set_price(200); // SerializeToString std::string gameInfoString; gameInfo.SerializeToString(&gameInfoString); // convert gameInfoString to jsval to send to javascript JSAutoCompartment ac(pGlobalContext, pGlobalObject); int length = gameInfoString.length(); JSObject* pMessageJSObject = JS_NewArrayBuffer(pGlobalContext, length); uint8_t* pMessageData = JS_GetArrayBufferData(pMessageJSObject); memcpy( (void*)pMessageData, (const void*)gameInfoString.c_str(), gameInfoString.length() ); JS::RootedValue ret(pGlobalContext); jsval arg = OBJECT_TO_JSVAL(pMessageJSObject); ScriptingCore::getInstance()->executeFunctionWithOwner( OBJECT_TO_JSVAL(pGlobalObject), "test_protobuf", 1, &arg, &ret );
Receive from C++
// cc_js_bridge.js test_protobuf = function (data) { var ProtoBuf = dcodeIO.ProtoBuf; var GameInfoBuilder = ProtoBuf.newBuilder(); // load .proto ProtoBuf.protoFromContents( "res/GameEnum.proto", GameInfoBuilder, jsb.fileUtils.getStringFromFile("res/GameEnum.proto") ); ProtoBuf.protoFromContents( "res/GameInfo.proto", GameInfoBuilder, jsb.fileUtils.getStringFromFile("res/GameInfo.proto") ); // decode protobuf message from C++ var Game = GameInfoBuilder.build("game"); var gameInfo = Game.info.GameInfo.decode(data, "utf8"); // ... }
Add a protoFromContents
function
to protobuf.js
:
ProtoBuf.protoFromContents = function(filename, builder, contents) { return contents === null ? null : ProtoBuf.loadProto( contents, builder, filename ); };
Send from javascript
var gameInfoOut = new Game.info.GameInfo(); var roleInfo1 = new Game.info.RoleInfo(); roleInfo1.name = "js_name1"; roleInfo1.type = Game.enumeration.RoleType.FIGHTER; gameInfoOut.roleInfo.push(roleInfo1); var roleInfo2 = new Game.info.RoleInfo(); roleInfo2.name = "js_name2"; roleInfo2.type = Game.enumeration.RoleType.BOWMAN; gameInfoOut.roleInfo.push(roleInfo2); var itemInfo1 = new Game.info.ItemInfo(); itemInfo1.name = "js_item1"; itemInfo1.price = 100; gameInfoOut.itemInfo.push(itemInfo1); var itemInfo2 = new Game.info.ItemInfo(); itemInfo2.name = "js_item2"; itemInfo2.price = 200; gameInfoOut.itemInfo.push(itemInfo2); return gameInfoOut.encodeAB();
Receive from javascript
// ... JS::RootedValue ret(pGlobalContext); jsval arg = OBJECT_TO_JSVAL(pMessageJSObject); ScriptingCore::getInstance()->executeFunctionWithOwner( OBJECT_TO_JSVAL(pGlobalObject), "test_protobuf", 1, &arg, &ret ); // convert JS::RootedValue ret to protobuf message game::info::GameInfo* pGameInfo = new game::info::GameInfo; JSObject* pRetJSObject = ret.toObjectOrNull(); if(pRetJSObject && JS_IsArrayBufferObject(pRetJSObject)) { uint8_t* pDataBuffer = nullptr; int dataBufferCount = 0; pDataBuffer = JS_GetArrayBufferData(pRetJSObject); dataBufferCount = JS_GetArrayBufferByteLength(pRetJSObject); pGameInfo->ParseFromArray(pDataBuffer, dataBufferCount); // ... }