├── .gitignore ├── .jshintrc ├── .project ├── LICENSE ├── README.md ├── component.json ├── lib ├── client │ └── protobuf.js ├── codec.js ├── constant.js ├── decoder.js ├── encoder.js ├── parser.js ├── protobuf.js └── util.js ├── package.json └── test ├── client ├── encoderTest.js ├── protobufTest.js └── rootMsgTest.js ├── codecTest.js ├── example.json ├── msg.json ├── protobufTest.js ├── protos.json ├── rootMsg.json ├── rootMsgTC.js ├── rootMsgTest.js ├── rootProtos.json ├── stringBufferTest.js ├── testMsg.js └── writeProtos.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | *.log 3 | .DS_Store -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "describe", 4 | "it", 5 | "before", 6 | "after", 7 | "window", 8 | "__resources__" 9 | ], 10 | "es5": true, 11 | "node": true, 12 | "eqeqeq": true, 13 | "undef": true, 14 | "curly": true, 15 | "bitwise": true, 16 | "immed": false, 17 | "newcap": true, 18 | "nonew": true, 19 | "white": false, 20 | "smarttabs": true, 21 | "strict": false 22 | } 23 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | pomelo-protobuf 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012 Netease, Inc. and other pomelo contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Pomelo-protobuf 2 | Protobuf protocol is a high efficient binary protocol for data encode, this module implement the protobuf protocol, and used in [pomelo](https://github.com/NetEase/pomelo) for data transfer. 3 | Of course, pomelo-protobuf can also be used independently in other projects. 4 | ##Architecture 5 | Unlike the google protobuf, we provide a universal encoder and decoder in pomelo-protobuf. We use protos file as meta data to encode/decode messages, so you do not need to add any code to your project, instead , what you need is to add a protos.json (or two for different encoder and decoder messages) files to define the message need to encode by protobuf.The architecture of pomelo-protobuf is as follow: 6 | 7 | ![pomelo protobuf](http://pomelo.netease.com/resource/documentImage/protocol/Protobuf_pomelo.png) 8 | 9 | ##Usage 10 | ###Define protos 11 | To use pomelo-protobuf, you need to write a JSON file to define the message format. The syntax of the file is as the same as the .proto file in protobuf, but in JSON format, here is the example protos.json: 12 | 13 | ``` 14 | { 15 | "onMove" : { 16 | "required uInt32 entityId" : 1, 17 | "message Path": { 18 | "required uInt32 x" : 1, 19 | "required uInt32 y" : 2 20 | }, 21 | "repeated Path path" : 2, 22 | "required uInt32 speed" : 3 23 | }, 24 | "onAttack" : { 25 | "required uInt32 attacker" : 1, 26 | "required uInt32 target" : 2, 27 | "message AttackResult" : { 28 | "required uInt32 result" : 1, 29 | "required uInt32 damage" : 2, 30 | "optional uInt32 exp" : 3 31 | }, 32 | "required AttackResult result" : 3 33 | } 34 | } 35 | ``` 36 | 37 | Unlike the google protobuf, we write all the protos in the same file, with a unique key to define the message. 38 | 39 | To use the protos, we use a parser to parse the protos file into more machine friendly format, which is also a json format, then you can use the result to decode/encode messages. 40 | 41 | ###RootMessage support 42 | you can write rootMessage in protos for global usage 43 | ``` 44 | { 45 | "message Path": { 46 | "required double x" : 1, 47 | "required double y" : 2 48 | }, 49 | "message Equipment" : { 50 | "required uInt32 entityId" : 1, 51 | "required uInt32 kindId" : 2 52 | }, 53 | "onMove" : { 54 | "required uInt32 entityId" : 1, 55 | "repeated Path path" : 2, 56 | "required float speed" : 3 57 | }, 58 | "area.playerHandler.enterScene" : { 59 | "message Player" : { 60 | "message Bag" : { 61 | "message Item" : { 62 | "required uInt32 id" : 1, 63 | "optional string type" : 2 64 | }, 65 | "repeated Item items" : 1 66 | }, 67 | "required uInt32 entityId" : 1, 68 | "required uInt32 kindId" : 2, 69 | "required Bag bag" : 3, 70 | "repeated Equipment equipments" : 4 71 | }, 72 | "optional Player curPlayer" : 2 73 | } 74 | } 75 | ``` 76 | 77 | ###Server side and Client side 78 | Pomelo-protobuf has server code and client code for js. 79 | 80 | - The server code run in Node.JS environment, use Buffer to represent the binary data. 81 | - The client side code run on browser, use ByteArray to represent the binary data. 82 | 83 | On average, the encode/decode speed of Server version is 60% faster than client version, with less memory usage. So we highly recommend that use the server code on Node.JS for better performance. 84 | 85 | ### Example message 86 | 87 | ``` 88 | var key = 'onMove'; 89 | var msg = { 90 | entityId : 14, 91 | path : [{x : 128,y : 796},{x : 677,y : 895}], 92 | speed : 160 93 | }; 94 | 95 | ``` 96 | 97 | ### Server side encode/decode 98 | 99 | ``` 100 | //Require proto buf module 101 | var protobuf = require('protobuf'); 102 | 103 | //Set encode protos and decode protos 104 | var protos = protobuf.parse(require('./protos.json')); 105 | protobuf.init({encoderProtos:protos, decoderProtos:protos}); 106 | 107 | //Encode msg to binary Buffer 108 | var buffer = protobuf.encode(key, msg); 109 | 110 | //Decode a msg from binary buffer 111 | var decodeMsg = protobuf.decode(key, buffer); 112 | 113 | ``` 114 | At server side, the encode result will be a Buffer. 115 | The encoderProtos and decodeProtos can be different, in this case we use the same protos for encoder and decoder. 116 | 117 | ### Client side encode/decode 118 | To use the protbuf as browser, you need to include the /client/protobuf.js in your html. 119 | 120 | ``` 121 | //Require proto buf 122 | var protobuf = require('protobuf'); 123 | 124 | //Get parsed protos from server 125 | var protos = getProtos(); 126 | 127 | //Init protobuf 128 | protobuf.init({encoderProtos:protos, decoderProtos:protos}); 129 | 130 | //Encode msg to binary Buffer 131 | var buffer = protobuf.encode(key, msg); 132 | 133 | //Decode a msg from binary buffer 134 | var decodeMsg = protobuf.decode(key, buffer); 135 | 136 | ``` 137 | 138 | The protobuf will be a global variable, and you need to get the parsed protos from server. 139 | The others are the same as in server side, except the encoder result will by a ByteArray instead of Buffer. 140 | 141 | ###Compatibility 142 | For the same message and proto, the encode results are **the same** for **pomelo-protobuf** and **google protobuf** .This means you can exchange binary data with google-protobuf. 143 | 144 | Some how we has some changes in the proto file, and there are some features we do not support, there are the different: 145 | 146 | - **package** : The array with simple content (integer, float) are packaged by default.And the complex content(message, string) are not packaged. 147 | 148 | - **long** : Pomelo protocol do not support long type, because there are no long int in javascript.All the integer bigger than 32 bits will be translate to a 64bit float, which has only has 52 bits significant figure. It will lost presion for any integer has more than 52 bits significant figures. 149 | 150 | - **default** : Pomelo-protobuf do not support default keyword, for the default value is only used to initialized the element at the decoder side, which can be done by the constructor. 151 | 152 | - **enum** : Pomelo-protobuf do not support the enum keyword. 153 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pomelo-protobuf", 3 | "description": "pomelo-protobuf", 4 | "keywords": [ 5 | "pomelo", 6 | "protobuf" 7 | ], 8 | "version": "0.2.0", 9 | "main": "lib/client/protobuf.js", 10 | "scripts": [ 11 | "lib/client/protobuf.js" 12 | ], 13 | "repo": "https://github.com/pomelonode/pomelo-protobuf" 14 | } -------------------------------------------------------------------------------- /lib/client/protobuf.js: -------------------------------------------------------------------------------- 1 | /* ProtocolBuffer client 0.1.0*/ 2 | 3 | /** 4 | * pomelo-protobuf 5 | * @author 6 | */ 7 | 8 | /** 9 | * Protocol buffer root 10 | * In browser, it will be window.protbuf 11 | */ 12 | (function (exports, global){ 13 | var Protobuf = exports; 14 | 15 | Protobuf.init = function(opts){ 16 | //On the serverside, use serverProtos to encode messages send to client 17 | Protobuf.encoder.init(opts.encoderProtos); 18 | 19 | //On the serverside, user clientProtos to decode messages receive from clients 20 | Protobuf.decoder.init(opts.decoderProtos); 21 | }; 22 | 23 | Protobuf.encode = function(key, msg){ 24 | return Protobuf.encoder.encode(key, msg); 25 | }; 26 | 27 | Protobuf.decode = function(key, msg){ 28 | return Protobuf.decoder.decode(key, msg); 29 | }; 30 | 31 | // exports to support for components 32 | module.exports = Protobuf; 33 | if(typeof(window) != "undefined") { 34 | window.protobuf = Protobuf; 35 | } 36 | 37 | })(typeof(window) == "undefined" ? module.exports : (this.protobuf = {}), this); 38 | 39 | /** 40 | * constants 41 | */ 42 | (function (exports, global){ 43 | var constants = exports.constants = {}; 44 | 45 | constants.TYPES = { 46 | uInt32 : 0, 47 | sInt32 : 0, 48 | int32 : 0, 49 | double : 1, 50 | string : 2, 51 | message : 2, 52 | float : 5 53 | }; 54 | 55 | })('undefined' !== typeof protobuf ? protobuf : module.exports, this); 56 | 57 | /** 58 | * util module 59 | */ 60 | (function (exports, global){ 61 | 62 | var Util = exports.util = {}; 63 | 64 | Util.isSimpleType = function(type){ 65 | return ( type === 'uInt32' || 66 | type === 'sInt32' || 67 | type === 'int32' || 68 | type === 'uInt64' || 69 | type === 'sInt64' || 70 | type === 'float' || 71 | type === 'double' ); 72 | }; 73 | 74 | })('undefined' !== typeof protobuf ? protobuf : module.exports, this); 75 | 76 | /** 77 | * codec module 78 | */ 79 | (function (exports, global){ 80 | 81 | var Codec = exports.codec = {}; 82 | 83 | var buffer = new ArrayBuffer(8); 84 | var float32Array = new Float32Array(buffer); 85 | var float64Array = new Float64Array(buffer); 86 | var uInt8Array = new Uint8Array(buffer); 87 | 88 | Codec.encodeUInt32 = function(n){ 89 | var n = parseInt(n); 90 | if(isNaN(n) || n < 0){ 91 | return null; 92 | } 93 | 94 | var result = []; 95 | do{ 96 | var tmp = n % 128; 97 | var next = Math.floor(n/128); 98 | 99 | if(next !== 0){ 100 | tmp = tmp + 128; 101 | } 102 | result.push(tmp); 103 | n = next; 104 | }while(n !== 0); 105 | 106 | return result; 107 | }; 108 | 109 | Codec.encodeSInt32 = function(n){ 110 | var n = parseInt(n); 111 | if(isNaN(n)){ 112 | return null; 113 | } 114 | n = n<0?(Math.abs(n)*2-1):n*2; 115 | 116 | return Codec.encodeUInt32(n); 117 | }; 118 | 119 | Codec.decodeUInt32 = function(bytes){ 120 | var n = 0; 121 | 122 | for(var i = 0; i < bytes.length; i++){ 123 | var m = parseInt(bytes[i]); 124 | n = n + ((m & 0x7f) * Math.pow(2,(7*i))); 125 | if(m < 128){ 126 | return n; 127 | } 128 | } 129 | 130 | return n; 131 | }; 132 | 133 | Codec.decodeSInt32 = function(bytes){ 134 | var n = this.decodeUInt32(bytes); 135 | var flag = ((n%2) === 1)?-1:1; 136 | 137 | n = ((n%2 + n)/2)*flag; 138 | 139 | return n; 140 | }; 141 | 142 | Codec.encodeFloat = function(float){ 143 | float32Array[0] = float; 144 | return uInt8Array; 145 | }; 146 | 147 | Codec.decodeFloat = function(bytes, offset){ 148 | if(!bytes || bytes.length < (offset + 4)){ 149 | return null; 150 | } 151 | 152 | for(var i = 0; i < 4; i++){ 153 | uInt8Array[i] = bytes[offset + i]; 154 | } 155 | 156 | return float32Array[0]; 157 | }; 158 | 159 | Codec.encodeDouble = function(double){ 160 | float64Array[0] = double; 161 | return uInt8Array.subarray(0, 8); 162 | }; 163 | 164 | Codec.decodeDouble = function(bytes, offset){ 165 | if(!bytes || bytes.length < (offset + 8)){ 166 | return null; 167 | } 168 | 169 | for(var i = 0; i < 8; i++){ 170 | uInt8Array[i] = bytes[offset + i]; 171 | } 172 | 173 | return float64Array[0]; 174 | }; 175 | 176 | Codec.encodeStr = function(bytes, offset, str){ 177 | for(var i = 0; i < str.length; i++){ 178 | var code = str.charCodeAt(i); 179 | var codes = encode2UTF8(code); 180 | 181 | for(var j = 0; j < codes.length; j++){ 182 | bytes[offset] = codes[j]; 183 | offset++; 184 | } 185 | } 186 | 187 | return offset; 188 | }; 189 | 190 | /** 191 | * Decode string from utf8 bytes 192 | */ 193 | Codec.decodeStr = function(bytes, offset, length){ 194 | var array = []; 195 | var end = offset + length; 196 | 197 | while(offset < end){ 198 | var code = 0; 199 | 200 | if(bytes[offset] < 128){ 201 | code = bytes[offset]; 202 | 203 | offset += 1; 204 | }else if(bytes[offset] < 224){ 205 | code = ((bytes[offset] & 0x3f)<<6) + (bytes[offset+1] & 0x3f); 206 | offset += 2; 207 | }else{ 208 | code = ((bytes[offset] & 0x0f)<<12) + ((bytes[offset+1] & 0x3f)<<6) + (bytes[offset+2] & 0x3f); 209 | offset += 3; 210 | } 211 | 212 | array.push(code); 213 | 214 | } 215 | 216 | var str = ''; 217 | for(var i = 0; i < array.length;){ 218 | str += String.fromCharCode.apply(null, array.slice(i, i + 10000)); 219 | i += 10000; 220 | } 221 | 222 | return str; 223 | }; 224 | 225 | /** 226 | * Return the byte length of the str use utf8 227 | */ 228 | Codec.byteLength = function(str){ 229 | if(typeof(str) !== 'string'){ 230 | return -1; 231 | } 232 | 233 | var length = 0; 234 | 235 | for(var i = 0; i < str.length; i++){ 236 | var code = str.charCodeAt(i); 237 | length += codeLength(code); 238 | } 239 | 240 | return length; 241 | }; 242 | 243 | /** 244 | * Encode a unicode16 char code to utf8 bytes 245 | */ 246 | function encode2UTF8(charCode){ 247 | if(charCode <= 0x7f){ 248 | return [charCode]; 249 | }else if(charCode <= 0x7ff){ 250 | return [0xc0|(charCode>>6), 0x80|(charCode & 0x3f)]; 251 | }else{ 252 | return [0xe0|(charCode>>12), 0x80|((charCode & 0xfc0)>>6), 0x80|(charCode & 0x3f)]; 253 | } 254 | } 255 | 256 | function codeLength(code){ 257 | if(code <= 0x7f){ 258 | return 1; 259 | }else if(code <= 0x7ff){ 260 | return 2; 261 | }else{ 262 | return 3; 263 | } 264 | } 265 | })('undefined' !== typeof protobuf ? protobuf : module.exports, this); 266 | 267 | /** 268 | * encoder module 269 | */ 270 | (function (exports, global){ 271 | 272 | var protobuf = exports; 273 | var MsgEncoder = exports.encoder = {}; 274 | 275 | var codec = protobuf.codec; 276 | var constant = protobuf.constants; 277 | var util = protobuf.util; 278 | 279 | MsgEncoder.init = function(protos){ 280 | this.protos = protos || {}; 281 | }; 282 | 283 | MsgEncoder.encode = function(route, msg){ 284 | //Get protos from protos map use the route as key 285 | var protos = this.protos[route]; 286 | 287 | //Check msg 288 | if(!checkMsg(msg, protos)){ 289 | return null; 290 | } 291 | 292 | //Set the length of the buffer 2 times bigger to prevent overflow 293 | var length = codec.byteLength(JSON.stringify(msg)); 294 | 295 | //Init buffer and offset 296 | var buffer = new ArrayBuffer(length); 297 | var uInt8Array = new Uint8Array(buffer); 298 | var offset = 0; 299 | 300 | if(!!protos){ 301 | offset = encodeMsg(uInt8Array, offset, protos, msg); 302 | if(offset > 0){ 303 | return uInt8Array.subarray(0, offset); 304 | } 305 | } 306 | 307 | return null; 308 | }; 309 | 310 | /** 311 | * Check if the msg follow the defination in the protos 312 | */ 313 | function checkMsg(msg, protos){ 314 | if(!protos){ 315 | return false; 316 | } 317 | 318 | for(var name in protos){ 319 | var proto = protos[name]; 320 | 321 | //All required element must exist 322 | switch(proto.option){ 323 | case 'required' : 324 | if(typeof(msg[name]) === 'undefined'){ 325 | console.warn('no property exist for required! name: %j, proto: %j, msg: %j', name, proto, msg); 326 | return false; 327 | } 328 | case 'optional' : 329 | if(typeof(msg[name]) !== 'undefined'){ 330 | var message = protos.__messages[proto.type] || MsgEncoder.protos['message ' + proto.type]; 331 | if(!!message && !checkMsg(msg[name], message)){ 332 | console.warn('inner proto error! name: %j, proto: %j, msg: %j', name, proto, msg); 333 | return false; 334 | } 335 | } 336 | break; 337 | case 'repeated' : 338 | //Check nest message in repeated elements 339 | var message = protos.__messages[proto.type] || MsgEncoder.protos['message ' + proto.type]; 340 | if(!!msg[name] && !!message){ 341 | for(var i = 0; i < msg[name].length; i++){ 342 | if(!checkMsg(msg[name][i], message)){ 343 | return false; 344 | } 345 | } 346 | } 347 | break; 348 | } 349 | } 350 | 351 | return true; 352 | } 353 | 354 | function encodeMsg(buffer, offset, protos, msg){ 355 | for(var name in msg){ 356 | if(!!protos[name]){ 357 | var proto = protos[name]; 358 | 359 | switch(proto.option){ 360 | case 'required' : 361 | case 'optional' : 362 | offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag)); 363 | offset = encodeProp(msg[name], proto.type, offset, buffer, protos); 364 | break; 365 | case 'repeated' : 366 | if(msg[name].length > 0){ 367 | offset = encodeArray(msg[name], proto, offset, buffer, protos); 368 | } 369 | break; 370 | } 371 | } 372 | } 373 | 374 | return offset; 375 | } 376 | 377 | function encodeProp(value, type, offset, buffer, protos){ 378 | switch(type){ 379 | case 'uInt32': 380 | offset = writeBytes(buffer, offset, codec.encodeUInt32(value)); 381 | break; 382 | case 'int32' : 383 | case 'sInt32': 384 | offset = writeBytes(buffer, offset, codec.encodeSInt32(value)); 385 | break; 386 | case 'float': 387 | writeBytes(buffer, offset, codec.encodeFloat(value)); 388 | offset += 4; 389 | break; 390 | case 'double': 391 | writeBytes(buffer, offset, codec.encodeDouble(value)); 392 | offset += 8; 393 | break; 394 | case 'string': 395 | var length = codec.byteLength(value); 396 | 397 | //Encode length 398 | offset = writeBytes(buffer, offset, codec.encodeUInt32(length)); 399 | //write string 400 | codec.encodeStr(buffer, offset, value); 401 | offset += length; 402 | break; 403 | default : 404 | var message = protos.__messages[type] || MsgEncoder.protos['message ' + type]; 405 | if(!!message){ 406 | //Use a tmp buffer to build an internal msg 407 | var tmpBuffer = new ArrayBuffer(codec.byteLength(JSON.stringify(value))*2); 408 | var length = 0; 409 | 410 | length = encodeMsg(tmpBuffer, length, message, value); 411 | //Encode length 412 | offset = writeBytes(buffer, offset, codec.encodeUInt32(length)); 413 | //contact the object 414 | for(var i = 0; i < length; i++){ 415 | buffer[offset] = tmpBuffer[i]; 416 | offset++; 417 | } 418 | } 419 | break; 420 | } 421 | 422 | return offset; 423 | } 424 | 425 | /** 426 | * Encode reapeated properties, simple msg and object are decode differented 427 | */ 428 | function encodeArray(array, proto, offset, buffer, protos){ 429 | var i = 0; 430 | 431 | if(util.isSimpleType(proto.type)){ 432 | offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag)); 433 | offset = writeBytes(buffer, offset, codec.encodeUInt32(array.length)); 434 | for(i = 0; i < array.length; i++){ 435 | offset = encodeProp(array[i], proto.type, offset, buffer); 436 | } 437 | }else{ 438 | for(i = 0; i < array.length; i++){ 439 | offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag)); 440 | offset = encodeProp(array[i], proto.type, offset, buffer, protos); 441 | } 442 | } 443 | 444 | return offset; 445 | } 446 | 447 | function writeBytes(buffer, offset, bytes){ 448 | for(var i = 0; i < bytes.length; i++, offset++){ 449 | buffer[offset] = bytes[i]; 450 | } 451 | 452 | return offset; 453 | } 454 | 455 | function encodeTag(type, tag){ 456 | var value = constant.TYPES[type]||2; 457 | 458 | return codec.encodeUInt32((tag<<3)|value); 459 | } 460 | })('undefined' !== typeof protobuf ? protobuf : module.exports, this); 461 | 462 | /** 463 | * decoder module 464 | */ 465 | (function (exports, global){ 466 | var protobuf = exports; 467 | var MsgDecoder = exports.decoder = {}; 468 | 469 | var codec = protobuf.codec; 470 | var util = protobuf.util; 471 | 472 | var buffer; 473 | var offset = 0; 474 | 475 | MsgDecoder.init = function(protos){ 476 | this.protos = protos || {}; 477 | }; 478 | 479 | MsgDecoder.setProtos = function(protos){ 480 | if(!!protos){ 481 | this.protos = protos; 482 | } 483 | }; 484 | 485 | MsgDecoder.decode = function(route, buf){ 486 | var protos = this.protos[route]; 487 | 488 | buffer = buf; 489 | offset = 0; 490 | 491 | if(!!protos){ 492 | return decodeMsg({}, protos, buffer.length); 493 | } 494 | 495 | return null; 496 | }; 497 | 498 | function decodeMsg(msg, protos, length){ 499 | while(offset>3 537 | }; 538 | } 539 | 540 | /** 541 | * Get tag head without move the offset 542 | */ 543 | function peekHead(){ 544 | var tag = codec.decodeUInt32(peekBytes()); 545 | 546 | return { 547 | type : tag&0x7, 548 | tag : tag>>3 549 | }; 550 | } 551 | 552 | function decodeProp(type, protos){ 553 | switch(type){ 554 | case 'uInt32': 555 | return codec.decodeUInt32(getBytes()); 556 | case 'int32' : 557 | case 'sInt32' : 558 | return codec.decodeSInt32(getBytes()); 559 | case 'float' : 560 | var float = codec.decodeFloat(buffer, offset); 561 | offset += 4; 562 | return float; 563 | case 'double' : 564 | var double = codec.decodeDouble(buffer, offset); 565 | offset += 8; 566 | return double; 567 | case 'string' : 568 | var length = codec.decodeUInt32(getBytes()); 569 | 570 | var str = codec.decodeStr(buffer, offset, length); 571 | offset += length; 572 | 573 | return str; 574 | default : 575 | var message = protos && (protos.__messages[type] || MsgDecoder.protos['message ' + type]); 576 | if(!!message){ 577 | var length = codec.decodeUInt32(getBytes()); 578 | var msg = {}; 579 | decodeMsg(msg, message, offset+length); 580 | return msg; 581 | } 582 | break; 583 | } 584 | } 585 | 586 | function decodeArray(array, type, protos){ 587 | if(util.isSimpleType(type)){ 588 | var length = codec.decodeUInt32(getBytes()); 589 | 590 | for(var i = 0; i < length; i++){ 591 | array.push(decodeProp(type)); 592 | } 593 | }else{ 594 | array.push(decodeProp(type, protos)); 595 | } 596 | } 597 | 598 | function getBytes(flag){ 599 | var bytes = []; 600 | var pos = offset; 601 | flag = flag || false; 602 | 603 | var b; 604 | 605 | do{ 606 | b = buffer[pos]; 607 | bytes.push(b); 608 | pos++; 609 | }while(b >= 128); 610 | 611 | if(!flag){ 612 | offset = pos; 613 | } 614 | return bytes; 615 | } 616 | 617 | function peekBytes(){ 618 | return getBytes(true); 619 | } 620 | 621 | })('undefined' !== typeof protobuf ? protobuf : module.exports, this); 622 | 623 | -------------------------------------------------------------------------------- /lib/codec.js: -------------------------------------------------------------------------------- 1 | var Encoder = module.exports; 2 | 3 | /** 4 | * [encode an uInt32, return a array of bytes] 5 | * @param {[integer]} num 6 | * @return {[array]} 7 | */ 8 | Encoder.encodeUInt32 = function(num){ 9 | var n = parseInt(num); 10 | if(isNaN(n) || n < 0){ 11 | console.log(n); 12 | return null; 13 | } 14 | 15 | var result = []; 16 | do{ 17 | var tmp = n % 128; 18 | var next = Math.floor(n/128); 19 | 20 | if(next !== 0){ 21 | tmp = tmp + 128; 22 | } 23 | result.push(tmp); 24 | n = next; 25 | } while(n !== 0); 26 | 27 | return result; 28 | }; 29 | 30 | /** 31 | * [encode a sInt32, return a byte array] 32 | * @param {[sInt32]} num The sInt32 need to encode 33 | * @return {[array]} A byte array represent the integer 34 | */ 35 | Encoder.encodeSInt32 = function(num){ 36 | var n = parseInt(num); 37 | if(isNaN(n)){ 38 | return null; 39 | } 40 | n = n<0?(Math.abs(n)*2-1):n*2; 41 | 42 | return Encoder.encodeUInt32(n); 43 | }; 44 | 45 | Encoder.decodeUInt32 = function(bytes){ 46 | var n = 0; 47 | 48 | for(var i = 0; i < bytes.length; i++){ 49 | var m = parseInt(bytes[i]); 50 | n = n + ((m & 0x7f) * Math.pow(2,(7*i))); 51 | if(m < 128){ 52 | return n; 53 | } 54 | } 55 | 56 | return n; 57 | }; 58 | 59 | 60 | Encoder.decodeSInt32 = function(bytes){ 61 | var n = this.decodeUInt32(bytes); 62 | var flag = ((n%2) === 1)?-1:1; 63 | 64 | n = ((n%2 + n)/2)*flag; 65 | 66 | return n; 67 | }; 68 | -------------------------------------------------------------------------------- /lib/constant.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | TYPES : { 3 | uInt32 : 0, 4 | sInt32 : 0, 5 | int32 : 0, 6 | double : 1, 7 | string : 2, 8 | message : 2, 9 | float : 5 10 | } 11 | } -------------------------------------------------------------------------------- /lib/decoder.js: -------------------------------------------------------------------------------- 1 | var codec = require('./codec'); 2 | var util = require('./util'); 3 | 4 | var Decoder = module.exports; 5 | 6 | var buffer; 7 | var offset = 0; 8 | 9 | Decoder.init = function(protos){ 10 | this.protos = protos || {}; 11 | }; 12 | 13 | Decoder.setProtos = function(protos){ 14 | if(!!protos){ 15 | this.protos = protos; 16 | } 17 | }; 18 | 19 | Decoder.decode = function(route, buf){ 20 | var protos = this.protos[route]; 21 | 22 | buffer = buf; 23 | offset = 0; 24 | 25 | if(!!protos){ 26 | return decodeMsg({}, protos, buffer.length); 27 | } 28 | 29 | return null; 30 | }; 31 | 32 | function decodeMsg(msg, protos, length){ 33 | while(offset>3 71 | }; 72 | } 73 | 74 | /** 75 | * Get tag head without move the offset 76 | */ 77 | function peekHead(){ 78 | var tag = codec.decodeUInt32(peekBytes()); 79 | 80 | return { 81 | type : tag&0x7, 82 | tag : tag>>3 83 | }; 84 | } 85 | 86 | function decodeProp(type, protos){ 87 | switch(type){ 88 | case 'uInt32': 89 | return codec.decodeUInt32(getBytes()); 90 | case 'int32' : 91 | case 'sInt32' : 92 | return codec.decodeSInt32(getBytes()); 93 | case 'float' : 94 | var float = buffer.readFloatLE(offset); 95 | offset += 4; 96 | return float; 97 | case 'double' : 98 | var double = buffer.readDoubleLE(offset); 99 | offset += 8; 100 | return double; 101 | case 'string' : 102 | var length = codec.decodeUInt32(getBytes()); 103 | 104 | var str = buffer.toString('utf8', offset, offset+length); 105 | offset += length; 106 | 107 | return str; 108 | default : 109 | var message = protos && (protos.__messages[type] || Decoder.protos['message ' + type]); 110 | if(message){ 111 | var length = codec.decodeUInt32(getBytes()); 112 | var msg = {}; 113 | decodeMsg(msg, message, offset+length); 114 | return msg; 115 | } 116 | break; 117 | } 118 | } 119 | 120 | function decodeArray(array, type, protos){ 121 | if(util.isSimpleType(type)){ 122 | var length = codec.decodeUInt32(getBytes()); 123 | 124 | for(var i = 0; i < length; i++){ 125 | array.push(decodeProp(type)); 126 | } 127 | }else{ 128 | array.push(decodeProp(type, protos)); 129 | } 130 | } 131 | 132 | function getBytes(flag){ 133 | var bytes = []; 134 | var pos = offset; 135 | flag = flag || false; 136 | 137 | var b; 138 | do{ 139 | var b = buffer.readUInt8(pos); 140 | bytes.push(b); 141 | pos++; 142 | }while(b >= 128); 143 | 144 | if(!flag){ 145 | offset = pos; 146 | } 147 | return bytes; 148 | } 149 | 150 | function peekBytes(){ 151 | return getBytes(true); 152 | } -------------------------------------------------------------------------------- /lib/encoder.js: -------------------------------------------------------------------------------- 1 | var codec = require('./codec'); 2 | var constant = require('./constant'); 3 | var util = require('./util'); 4 | 5 | var Encoder = module.exports; 6 | 7 | Encoder.init = function(protos){ 8 | this.protos = protos || {}; 9 | }; 10 | 11 | Encoder.encode = function(route, msg){ 12 | if(!route || !msg){ 13 | console.warn('Route or msg can not be null! route : %j, msg %j', route, msg); 14 | return null; 15 | } 16 | 17 | //Get protos from protos map use the route as key 18 | var protos = this.protos[route]; 19 | 20 | //Check msg 21 | if(!checkMsg(msg, protos)){ 22 | console.warn('check msg failed! msg : %j, proto : %j', msg, protos); 23 | return null; 24 | } 25 | 26 | //Set the length of the buffer 2 times bigger to prevent overflow 27 | var length = Buffer.byteLength(JSON.stringify(msg))*2; 28 | 29 | //Init buffer and offset 30 | var buffer = new Buffer(length); 31 | var offset = 0; 32 | 33 | if(!!protos){ 34 | offset = encodeMsg(buffer, offset, protos, msg); 35 | if(offset > 0){ 36 | return buffer.slice(0, offset); 37 | } 38 | } 39 | 40 | return null; 41 | }; 42 | 43 | /** 44 | * Check if the msg follow the defination in the protos 45 | */ 46 | function checkMsg(msg, protos){ 47 | if(!protos || !msg){ 48 | console.warn('no protos or msg exist! msg : %j, protos : %j', msg, protos); 49 | return false; 50 | } 51 | 52 | for(var name in protos){ 53 | var proto = protos[name]; 54 | 55 | //All required element must exist 56 | switch(proto.option){ 57 | case 'required' : 58 | if(typeof(msg[name]) === 'undefined'){ 59 | console.warn('no property exist for required! name: %j, proto: %j, msg: %j', name, proto, msg); 60 | return false; 61 | } 62 | case 'optional' : 63 | if(typeof(msg[name]) !== 'undefined'){ 64 | var message = protos.__messages[proto.type] || Encoder.protos['message ' + proto.type]; 65 | if(!!message && !checkMsg(msg[name], message)){ 66 | console.warn('inner proto error! name: %j, proto: %j, msg: %j', name, proto, msg); 67 | return false; 68 | } 69 | } 70 | break; 71 | case 'repeated' : 72 | //Check nest message in repeated elements 73 | var message = protos.__messages[proto.type] || Encoder.protos['message ' + proto.type]; 74 | if(!!msg[name] && !!message){ 75 | for(var i = 0; i < msg[name].length; i++){ 76 | if(!checkMsg(msg[name][i], message)){ 77 | return false; 78 | } 79 | } 80 | } 81 | break; 82 | } 83 | } 84 | 85 | return true; 86 | } 87 | 88 | function encodeMsg(buffer, offset, protos, msg){ 89 | for(var name in msg){ 90 | if(!!protos[name]){ 91 | var proto = protos[name]; 92 | 93 | switch(proto.option){ 94 | case 'required' : 95 | case 'optional' : 96 | offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag)); 97 | offset = encodeProp(msg[name], proto.type, offset, buffer, protos); 98 | break; 99 | case 'repeated' : 100 | if(!!msg[name] && msg[name].length > 0){ 101 | offset = encodeArray(msg[name], proto, offset, buffer, protos); 102 | } 103 | break; 104 | } 105 | } 106 | } 107 | 108 | return offset; 109 | } 110 | 111 | function encodeProp(value, type, offset, buffer, protos){ 112 | var length = 0; 113 | 114 | switch(type){ 115 | case 'uInt32': 116 | offset = writeBytes(buffer, offset, codec.encodeUInt32(value)); 117 | break; 118 | case 'int32' : 119 | case 'sInt32': 120 | offset = writeBytes(buffer, offset, codec.encodeSInt32(value)); 121 | break; 122 | case 'float': 123 | buffer.writeFloatLE(value, offset); 124 | offset += 4; 125 | break; 126 | case 'double': 127 | buffer.writeDoubleLE(value, offset); 128 | offset += 8; 129 | break; 130 | case 'string': 131 | length = Buffer.byteLength(value); 132 | 133 | //Encode length 134 | offset = writeBytes(buffer, offset, codec.encodeUInt32(length)); 135 | //write string 136 | buffer.write(value, offset, length); 137 | offset += length; 138 | break; 139 | default : 140 | var message = protos.__messages[type] || Encoder.protos['message ' + type]; 141 | if(!!message){ 142 | //Use a tmp buffer to build an internal msg 143 | var tmpBuffer = new Buffer(Buffer.byteLength(JSON.stringify(value))*2); 144 | length = 0; 145 | 146 | length = encodeMsg(tmpBuffer, length, message, value); 147 | //Encode length 148 | offset = writeBytes(buffer, offset, codec.encodeUInt32(length)); 149 | //contact the object 150 | tmpBuffer.copy(buffer, offset, 0, length); 151 | 152 | offset += length; 153 | } 154 | break; 155 | } 156 | 157 | return offset; 158 | } 159 | 160 | /** 161 | * Encode reapeated properties, simple msg and object are decode differented 162 | */ 163 | function encodeArray(array, proto, offset, buffer, protos){ 164 | var i = 0; 165 | if(util.isSimpleType(proto.type)){ 166 | offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag)); 167 | offset = writeBytes(buffer, offset, codec.encodeUInt32(array.length)); 168 | for(i = 0; i < array.length; i++){ 169 | offset = encodeProp(array[i], proto.type, offset, buffer); 170 | } 171 | }else{ 172 | for(i = 0; i < array.length; i++){ 173 | offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag)); 174 | offset = encodeProp(array[i], proto.type, offset, buffer, protos); 175 | } 176 | } 177 | 178 | return offset; 179 | } 180 | 181 | function writeBytes(buffer, offset, bytes){ 182 | for(var i = 0; i < bytes.length; i++){ 183 | buffer.writeUInt8(bytes[i], offset); 184 | offset++; 185 | } 186 | 187 | return offset; 188 | } 189 | 190 | function encodeTag(type, tag){ 191 | var value = constant.TYPES[type]; 192 | 193 | if(value === undefined) value = 2; 194 | 195 | return codec.encodeUInt32((tag<<3)|value); 196 | } 197 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | var Parser = module.exports; 2 | 3 | /** 4 | * [parse the original protos, give the paresed result can be used by protobuf encode/decode.] 5 | * @param {[Object]} protos Original protos, in a js map. 6 | * @return {[Object]} The presed result, a js object represent all the meta data of the given protos. 7 | */ 8 | Parser.parse = function(protos){ 9 | var maps = {}; 10 | for(var key in protos){ 11 | maps[key] = parseObject(protos[key]); 12 | } 13 | 14 | return maps; 15 | }; 16 | 17 | /** 18 | * [parse a single protos, return a object represent the result. The method can be invocked recursively.] 19 | * @param {[Object]} obj The origin proto need to parse. 20 | * @return {[Object]} The parsed result, a js object. 21 | */ 22 | function parseObject(obj){ 23 | var proto = {}; 24 | var nestProtos = {}; 25 | var tags = {}; 26 | 27 | for(var name in obj){ 28 | var tag = obj[name]; 29 | var params = name.split(' '); 30 | 31 | switch(params[0]){ 32 | case 'message': 33 | if(params.length !== 2){ 34 | continue; 35 | } 36 | nestProtos[params[1]] = parseObject(tag); 37 | continue; 38 | case 'required': 39 | case 'optional': 40 | case 'repeated':{ 41 | //params length should be 3 and tag can't be duplicated 42 | if(params.length !== 3 || !!tags[tag]){ 43 | continue; 44 | } 45 | proto[params[2]] = { 46 | option : params[0], 47 | type : params[1], 48 | tag : tag 49 | }; 50 | tags[tag] = params[2]; 51 | } 52 | } 53 | } 54 | 55 | proto.__messages = nestProtos; 56 | proto.__tags = tags; 57 | return proto; 58 | } -------------------------------------------------------------------------------- /lib/protobuf.js: -------------------------------------------------------------------------------- 1 | var encoder = require('./encoder'); 2 | var decoder = require('./decoder'); 3 | var parser = require('./parser'); 4 | 5 | var Protobuf = module.exports; 6 | 7 | /** 8 | * [encode the given message, return a Buffer represent the message encoded by protobuf] 9 | * @param {[type]} key The key to identify the message type. 10 | * @param {[type]} msg The message body, a js object. 11 | * @return {[type]} The binary encode result in a Buffer. 12 | */ 13 | Protobuf.encode = function(key, msg){ 14 | return encoder.encode(key, msg); 15 | }; 16 | 17 | Protobuf.encode2Bytes = function(key, msg){ 18 | var buffer = this.encode(key, msg); 19 | if(!buffer || !buffer.length){ 20 | console.warn('encode msg failed! key : %j, msg : %j', key, msg); 21 | return null; 22 | } 23 | var bytes = new Uint8Array(buffer.length); 24 | for(var offset = 0; offset < buffer.length; offset++){ 25 | bytes[offset] = buffer.readUInt8(offset); 26 | } 27 | 28 | return bytes; 29 | }; 30 | 31 | Protobuf.encodeStr = function(key, msg, code){ 32 | code = code || 'base64'; 33 | var buffer = Protobuf.encode(key, msg); 34 | return !!buffer?buffer.toString(code):buffer; 35 | }; 36 | 37 | Protobuf.decode = function(key, msg){ 38 | return decoder.decode(key, msg); 39 | }; 40 | 41 | Protobuf.decodeStr = function(key, str, code){ 42 | code = code || 'base64'; 43 | var buffer = new Buffer(str, code); 44 | 45 | return !!buffer?Protobuf.decode(key, buffer):buffer; 46 | }; 47 | 48 | Protobuf.parse = function(json){ 49 | return parser.parse(json); 50 | }; 51 | 52 | Protobuf.setEncoderProtos = function(protos){ 53 | encoder.init(protos); 54 | }; 55 | 56 | Protobuf.setDecoderProtos = function(protos){ 57 | decoder.init(protos); 58 | }; 59 | 60 | Protobuf.init = function(opts){ 61 | //On the serverside, use serverProtos to encode messages send to client 62 | encoder.init(opts.encoderProtos); 63 | 64 | //On the serverside, user clientProtos to decode messages receive from clients 65 | decoder.init(opts.decoderProtos); 66 | 67 | }; -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | var util = module.exports; 2 | 3 | util.isSimpleType = function(type){ 4 | return ( type === 'uInt32' || 5 | type === 'sInt32' || 6 | type === 'int32' || 7 | type === 'uInt64' || 8 | type === 'sInt64' || 9 | type === 'float' || 10 | type === 'double'); 11 | }; 12 | 13 | util.equal = function(obj0, obj1){ 14 | for(var key in obj0){ 15 | var m = obj0[key]; 16 | var n = obj1[key]; 17 | 18 | if(typeof(m) === 'object'){ 19 | if(!util.equal(m, n)){ 20 | return false; 21 | } 22 | }else if(m !== n){ 23 | return false; 24 | } 25 | } 26 | 27 | return true; 28 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pomelo-protobuf", 3 | "version": "0.4.0", 4 | "main": "./lib/protobuf", 5 | "dependencies": {}, 6 | "author": { 7 | "name": "XiaogangZhang", 8 | "email": "zhang0925@gmail.com" 9 | }, 10 | "devDependencies": { 11 | "should": ">=0.0.1", 12 | "mocha": ">=0.0.1" 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /test/client/encoderTest.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var encoder = require('../../lib/client/protobuf').codec; 3 | 4 | describe('client encoder test', function(){ 5 | describe('float test for 10000 times', function(){ 6 | for(var i = 0; i < 10000; i++){ 7 | var float = Math.random(); 8 | 9 | var bytes = encoder.encodeFloat(float); 10 | var result = encoder.decodeFloat(bytes, 0); 11 | 12 | var diff = Math.abs(float-result); 13 | //console.log('float : %j, result : %j, diff : %j', float, result, diff); 14 | diff.should.below(0.0000001); 15 | 16 | } 17 | }); 18 | 19 | describe('double test for 10000 times', function(){ 20 | for(var i = 0; i < 10000; i++){ 21 | var double = Math.random(); 22 | 23 | var bytes = encoder.encodeDouble(double); 24 | var result = encoder.decodeDouble(bytes, 0); 25 | 26 | double.should.equal(result); 27 | } 28 | }); 29 | 30 | describe('utf8 encode & decode test, use 1000 * 1000 test case', function(){ 31 | var num = 1000; 32 | var limit = 1000; 33 | for(var i = 0; i < num; i++){ 34 | var strLength = Math.ceil(Math.random()*limit); 35 | var arr = []; 36 | for(var j = 0; j < strLength; j++){ 37 | arr.push(Math.floor(Math.random()*65536)); 38 | } 39 | //arr = [ 58452, 127, 38641, 25796, 20652, 19237 ]; 40 | 41 | var str = String.fromCharCode.apply(null, arr); 42 | 43 | //console.log('old arr : %j', arr); 44 | 45 | var length = encoder.byteLength(str); 46 | var buffer = new ArrayBuffer(length); 47 | var bytes = new Uint8Array(buffer); 48 | 49 | var offset = encoder.encodeStr(bytes, 0, str); 50 | //console.log('encode over, offset : %j, length : %j, str length : %j', offset, length, str.length); 51 | //console.log(bytes); 52 | length.should.equal.offset; 53 | 54 | var result = encoder.decodeStr(bytes, 0, length); 55 | 56 | 57 | str.length.should.equal(result.length); 58 | var flag = true; 59 | for(var m = 0; m < str.length; m++){ 60 | if(str.charCodeAt(m) != result.charCodeAt(m)){ 61 | console.log('error ! origin : %j, result : %j, code : %j, code 1 : %j', str, result, str.charCodeAt(m), result.charCodeAt(m)); 62 | console.log(arr); 63 | flag = false; 64 | } 65 | } 66 | 67 | if(!flag)return; 68 | //console.log('str : %j, bytes : %j, result : %j', str, bytes, result); 69 | } 70 | }); 71 | 72 | describe('string decode speed test', function(){ 73 | var array = []; 74 | var length = 100000; 75 | for(var i = 0; i < length; i++,array.push(0)); 76 | var start = Date.now(); 77 | var str = ''; 78 | for(var j = 0; j < length; ){ 79 | str += String.fromCharCode.apply(null, array.slice(j, j+10000)); 80 | j += 10000; 81 | } 82 | //var str = String.fromCharCode.apply(null, array); 83 | var end = Date.now(); 84 | 85 | console.log('cost time with fromCharCode method : %j, length : %j', end-start, str.length); 86 | 87 | start = Date.now(); 88 | str = ''; 89 | for(var i = 0; i < length; i++){ 90 | str += array[i]; 91 | } 92 | end = Date.now(); 93 | 94 | console.log('cost time by add string: %j, length : %j', end-start, str.length); 95 | }); 96 | }); -------------------------------------------------------------------------------- /test/client/protobufTest.js: -------------------------------------------------------------------------------- 1 | var protobuf = require('../../lib/client/protobuf'); 2 | var protobufServer = require('../../lib/protobuf'); 3 | var encoder = protobuf.encoder; 4 | var decoder = protobuf.decoder; 5 | var codec = protobuf.codec; 6 | var parser = require('../../lib/parser'); 7 | var util = require('../../lib/util'); 8 | var should = require('should'); 9 | var tc = require('../testMsg'); 10 | 11 | describe('msgEncoderTest', function(){ 12 | 13 | var protos = parser.parse(require('../example.json')); 14 | 15 | protobuf.init({encoderProtos:protos, decoderProtos:protos}); 16 | protobufServer.init({encoderProtos:protos, decoderProtos:protos}); 17 | 18 | describe('protobufTest', function(){ 19 | for(var route in tc){ 20 | var msg = tc[route]; 21 | var buffer = protobuf.encode(route, msg); 22 | 23 | var decodeMsg = protobuf.decode(route, buffer); 24 | 25 | util.equal(msg, decodeMsg).should.equal(true); 26 | } 27 | }); 28 | }); 29 | 30 | function toBuffer(arr){ 31 | var buffer = new Buffer(arr.length); 32 | 33 | for(var i = 0; i < arr.length; i++){ 34 | buffer.writeUInt8(arr[i], i); 35 | } 36 | 37 | return buffer; 38 | } -------------------------------------------------------------------------------- /test/client/rootMsgTest.js: -------------------------------------------------------------------------------- 1 | var protobuf = require('../../lib/client/protobuf'); 2 | var encoder = protobuf.encoder; 3 | var decoder = protobuf.decoder; 4 | var codec = protobuf.codec; 5 | var parser = require('../../lib/parser'); 6 | var util = require('../../lib/util'); 7 | var should = require('should'); 8 | var tc = require('../rootMsgTC'); 9 | 10 | describe('msgEncoderTest', function(){ 11 | 12 | var protos = parser.parse(require('../rootMsg.json')); 13 | 14 | protobuf.init({encoderProtos:protos, decoderProtos:protos}); 15 | 16 | describe('protobufTest', function(){ 17 | for(var route in tc){ 18 | var msg = tc[route]; 19 | 20 | console.log('===================='); 21 | console.log(route); 22 | 23 | var buffer = protobuf.encode(route, msg); 24 | 25 | console.log(msg); 26 | console.log(buffer.length); 27 | 28 | var decodeMsg = protobuf.decode(route, buffer); 29 | 30 | console.log(decodeMsg); 31 | console.log('===================='); 32 | 33 | util.equal(msg, decodeMsg).should.equal(true); 34 | } 35 | }); 36 | }); -------------------------------------------------------------------------------- /test/codecTest.js: -------------------------------------------------------------------------------- 1 | var encoder = require('../lib/codec'); 2 | var should = require('should'); 3 | 4 | describe('encoder test', function(){ 5 | describe('uInt32 and uInt64 test, for encode and decode 10000 random number', function(){ 6 | var limit = 0x7fffffffffffffff; 7 | 8 | var count = 10000; 9 | for(var i = 0; i < count; i++){ 10 | var number = Math.ceil(Math.random()*limit); 11 | var result = encoder.decodeUInt32(encoder.encodeUInt32(number)); 12 | should.equal(number, result); 13 | } 14 | }); 15 | 16 | describe('sInt32 adn sInt64 test, for encode and decode 10000 random number', function(){ 17 | var limit = 0xfffffffffffff; 18 | 19 | for(var i = 0; i < 10000; i++){ 20 | var flag = Math.random>0.5?1:-1; 21 | var number = Math.ceil(Math.random()*limit)*flag; 22 | 23 | var result = encoder.decodeSInt32(encoder.encodeSInt32(number)); 24 | 25 | should.equal(number, result); 26 | } 27 | }); 28 | }); -------------------------------------------------------------------------------- /test/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "onMove" : { 3 | "required uInt32 entityId" : 1, 4 | "message Path": { 5 | "required double x" : 1, 6 | "required double y" : 2 7 | }, 8 | "repeated Path path" : 2, 9 | "required float speed" : 3 10 | }, 11 | "onAttack" : { 12 | "required uInt32 attacker" : 1, 13 | "required uInt32 target" : 2, 14 | "message AttackResult" : { 15 | "required uInt32 result" : 1, 16 | "required uInt32 damage" : 2, 17 | "repeated Item items" : 4, 18 | "message Item" : { 19 | "required uInt32 kindId" : 1, 20 | "required uInt32 x" : 2, 21 | "required uInt32 y" : 3, 22 | "required uInt32 entityId" : 4, 23 | "required uInt32 playerId" : 5, 24 | "required string type" : 6 25 | } 26 | }, 27 | "required AttackResult result" : 3, 28 | "optional uInt32 exp" : 4, 29 | "optional uInt32 reviveTime" : 5, 30 | "required uInt32 skillId" : 6 31 | }, 32 | "onUpgrade" : { 33 | "required uInt32 entityId" : 1, 34 | "required uInt32 kindId" : 2, 35 | "required uInt32 x" : 3, 36 | "required uInt32 y" : 4, 37 | "required uInt32 level" : 5, 38 | "required uInt32 walkSpeed" : 6, 39 | "required uInt32 hp" : 7, 40 | "required uInt32 maxHp" : 8, 41 | "required uInt32 mp" : 9, 42 | "required uInt32 maxMp" : 10, 43 | "required uInt32 id" : 11, 44 | "required string name" : 12, 45 | "required uInt32 experience" : 13, 46 | "required uInt32 attackValue" : 14, 47 | "required uInt32 defenceValue" : 15, 48 | "required double attackSpeed" : 16, 49 | "required uInt32 areaId" : 17, 50 | "required uInt32 hitRate" : 18, 51 | "required uInt32 dodgeRate" : 19, 52 | "required uInt32 nextLevelExp" : 20, 53 | "required uInt32 skillPoint" : 21, 54 | "required string kindName" : 22, 55 | "required string type" : 23 56 | }, 57 | "onPickItem" : { 58 | "required uInt32 player" : 1, 59 | "required uInt32 item" : 2, 60 | "required uInt32 index" : 3 61 | }, 62 | "onNPCTalk" : { 63 | "required uInt32 npc" : 1, 64 | "required string npcword" : 2, 65 | "required string myword" : 3 66 | }, 67 | "onRevive" : { 68 | "required uInt32 entityId" : 1, 69 | "required uInt32 x" : 2, 70 | "required uInt32 y" : 3, 71 | "required uInt32 hp" : 4 72 | }, 73 | "onAddEntities" : { 74 | "message NPC" : { 75 | "required uInt32 entityId" : 1, 76 | "required uInt32 kindId" : 2, 77 | "required uInt32 x" : 3, 78 | "required uInt32 y" : 4 79 | }, 80 | "message Mob" : { 81 | "required uInt32 entityId" : 1, 82 | "required uInt32 kindId" : 2, 83 | "required uInt32 x" : 3, 84 | "required uInt32 y" : 4, 85 | "required uInt32 level" : 5, 86 | "required uInt32 walkSpeed" : 6, 87 | "required uInt32 hp" : 7, 88 | "required uInt32 maxHp" : 8 89 | }, 90 | "message Item" : { 91 | "required uInt32 entityId" : 1, 92 | "required uInt32 kindId" : 2, 93 | "required uInt32 x" : 3, 94 | "required uInt32 y" : 4, 95 | "required uInt32 playerId" : 5 96 | }, 97 | "message Equipment" : { 98 | "required uInt32 entityId" : 1, 99 | "required uInt32 kindId" : 2, 100 | "required uInt32 x" : 3, 101 | "required uInt32 y" : 4, 102 | "required uInt32 playerId" : 5 103 | }, 104 | "message Player" : { 105 | "required uInt32 entityId" : 1, 106 | "required uInt32 kindId" : 2, 107 | "required uInt32 x" : 3, 108 | "required uInt32 y" : 4, 109 | "required uInt32 level" : 5, 110 | "required uInt32 walkSpeed" : 6, 111 | "required uInt32 hp" : 7, 112 | "required uInt32 maxHp" : 8, 113 | "required uInt32 mp" : 9, 114 | "required uInt32 maxMp" : 10, 115 | "required uInt32 id" : 11, 116 | "required string name" : 12 117 | }, 118 | "repeated NPC npc" : 1, 119 | "repeated Mob mob" : 2, 120 | "repeated Item item" : 3, 121 | "repeated Equipment euipment" : 4, 122 | "repeated Player player" : 5 123 | }, 124 | "onRemoveEntities" : { 125 | "repeated uInt32 entities" : 1 126 | }, 127 | "onPathCheckOut" : { 128 | "required uInt32 entityId" : 1, 129 | "message Position" : { 130 | "required uInt32 x" : 1, 131 | "required uInt32 y" : 2 132 | }, 133 | "required Position position" : 2 134 | }, 135 | "area.playerHandler.enterScene" : { 136 | "message Entities" : { 137 | "message NPC" : { 138 | "required uInt32 entityId" : 1, 139 | "required uInt32 kindId" : 2, 140 | "required uInt32 x" : 3, 141 | "required uInt32 y" : 4 142 | }, 143 | "message Mob" : { 144 | "required uInt32 entityId" : 1, 145 | "required uInt32 kindId" : 2, 146 | "required uInt32 x" : 3, 147 | "required uInt32 y" : 4, 148 | "required uInt32 level" : 5, 149 | "required uInt32 walkSpeed" : 6, 150 | "required uInt32 hp" : 7, 151 | "required uInt32 maxHp" : 8 152 | }, 153 | "message Item" : { 154 | "required uInt32 entityId" : 1, 155 | "required uInt32 kindId" : 2, 156 | "required uInt32 x" : 3, 157 | "required uInt32 y" : 4, 158 | "required uInt32 playerId" : 5 159 | }, 160 | "message Equipment" : { 161 | "required uInt32 entityId" : 1, 162 | "required uInt32 kindId" : 2, 163 | "required uInt32 x" : 3, 164 | "required uInt32 y" : 4, 165 | "required uInt32 playerId" : 5 166 | }, 167 | "message Player" : { 168 | "required uInt32 entityId" : 1, 169 | "required uInt32 kindId" : 2, 170 | "required uInt32 x" : 3, 171 | "required uInt32 y" : 4, 172 | "required uInt32 level" : 5, 173 | "required uInt32 walkSpeed" : 6, 174 | "required uInt32 hp" : 7, 175 | "required uInt32 maxHp" : 8, 176 | "required uInt32 mp" : 9, 177 | "required uInt32 maxMp" : 10, 178 | "required uInt32 id" : 11, 179 | "required string name" : 12 180 | }, 181 | "repeated NPC npc" : 1, 182 | "repeated Mob mob" : 2, 183 | "repeated Item item" : 3, 184 | "repeated Equipment euipment" : 4, 185 | "repeated Player player" : 5 186 | }, 187 | "message Player" : { 188 | "message Bag" : { 189 | "message Item" : { 190 | "required uInt32 key" : 1, 191 | "required uInt32 id" : 2, 192 | "required string type" : 3 193 | }, 194 | "required uInt32 itemCount" : 1, 195 | "repeated Item items" : 2 196 | }, 197 | "message Equipments" : { 198 | "required uInt32 weapon" : 1, 199 | "required uInt32 armor" : 2, 200 | "required uInt32 helmet" : 3, 201 | "required uInt32 necklace" : 4, 202 | "required uInt32 ring" : 5, 203 | "required uInt32 belt" : 6, 204 | "required uInt32 shoes" : 7, 205 | "required uInt32 legguard" : 8, 206 | "required uInt32 amulet" : 9 207 | }, 208 | "message FightSkill" : { 209 | "required uInt32 id" : 1, 210 | "required uInt32 level" : 2 211 | }, 212 | "required uInt32 entityId" : 1, 213 | "required uInt32 kindId" : 2, 214 | "required uInt32 x" : 3, 215 | "required uInt32 y" : 4, 216 | "required uInt32 level" : 5, 217 | "required uInt32 walkSpeed" : 6, 218 | "required uInt32 hp" : 7, 219 | "required uInt32 maxHp" : 8, 220 | "required uInt32 mp" : 9, 221 | "required uInt32 maxMp" : 10, 222 | "required uInt32 id" : 11, 223 | "required string name" : 12, 224 | "required uInt32 experience" : 13, 225 | "required uInt32 attackValue" : 14, 226 | "required uInt32 defenceValue" : 15, 227 | "required double attackSpeed" : 16, 228 | "required uInt32 areaId" : 17, 229 | "required uInt32 hitRate" : 18, 230 | "required uInt32 dodgeRate" : 19, 231 | "required uInt32 nextLevelExp" : 20, 232 | "required uInt32 skillPoint" : 21, 233 | "required string type" : 22, 234 | "required Bag bag" : 23, 235 | "required Equipments equipments" : 24, 236 | "repeated FightSkill fightSkills" : 25 237 | }, 238 | "optional Entities entities" : 1, 239 | "optional Player curPlayer" : 2, 240 | "message Map" : { 241 | "message Collisions" : { 242 | "message Collison" : { 243 | "required uInt32 start" : 1, 244 | "required uInt32 length" : 2 245 | }, 246 | "repeated Collison collisions" : 1 247 | }, 248 | "required string name" : 1, 249 | "required uInt32 width" : 2, 250 | "required uInt32 height" : 3, 251 | "required uInt32 tileW" : 4, 252 | "required uInt32 tileH" : 5, 253 | "repeated Collisions weightMap" : 6 254 | }, 255 | "required Map map" : 3 256 | } 257 | } -------------------------------------------------------------------------------- /test/msg.json: -------------------------------------------------------------------------------- 1 | { 2 | "area.playerHandler.enterScene": { 3 | "map": { 4 | "name": "a", 5 | "width": 10, 6 | "height": 10, 7 | "tileW": 5, 8 | "tileH": 5, 9 | "weightMap": [ 10 | { 11 | "collisions": [] 12 | }, 13 | { 14 | "collisions": [] 15 | }, 16 | { 17 | "collisions": [ 18 | { 19 | "start": 1, 20 | "length": 3 21 | }, 22 | { 23 | "start": 79, 24 | "length": 3 25 | } 26 | ] 27 | }, 28 | { 29 | "collisions": [ 30 | { 31 | "start": 27, 32 | "length": 2 33 | }, 34 | { 35 | "start": 78, 36 | "length": 4 37 | } 38 | ] 39 | } 40 | ] 41 | } 42 | }, 43 | "onMove": { 44 | "entityId": 14, 45 | "path": [ 46 | { 47 | "x": 10.4, 48 | "y": 90.9 49 | }, 50 | { 51 | "x": 10.4, 52 | "y": 90.1 53 | } 54 | ], 55 | "speed": 160.0 56 | }, 57 | "onUpgrade": { 58 | "id": 32726, 59 | "entityId": 48, 60 | "name": "super1", 61 | "kindId": 210, 62 | "kindName": "Angle", 63 | "type": "player", 64 | "x": 755, 65 | "y": 608, 66 | "hp": 352, 67 | "mp": 32, 68 | "maxHp": 352, 69 | "maxMp": 32, 70 | "level": 3, 71 | "experience": 2, 72 | "attackValue": 37, 73 | "defenceValue": 15, 74 | "walkSpeed": 240, 75 | "attackSpeed": 1.2, 76 | "areaId": 1, 77 | "hitRate": 90, 78 | "dodgeRate": 13, 79 | "nextLevelExp": 114, 80 | "skillPoint": 3 81 | } 82 | } -------------------------------------------------------------------------------- /test/protobufTest.js: -------------------------------------------------------------------------------- 1 | var protobuf = require('../lib/protobuf'); 2 | var util = require('../lib/util'); 3 | var should = require('should'); 4 | var tc = require('./testMsg'); 5 | 6 | 7 | describe('msgEncoderTest', function(){ 8 | var protos = protobuf.parse(require('./example.json')); 9 | protobuf.init({encoderProtos:protos, decoderProtos:protos}); 10 | 11 | describe('encodeTest', function(){ 12 | for(var route in tc){ 13 | var msg = tc[route]; 14 | var buffer = protobuf.encode(route, msg); 15 | 16 | console.log(msg); 17 | console.log(buffer.length); 18 | console.log(buffer) 19 | 20 | var decodeMsg = protobuf.decode(route, buffer); 21 | 22 | console.log(decodeMsg); 23 | 24 | util.equal(msg, decodeMsg).should.equal(true); 25 | } 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/protos.json: -------------------------------------------------------------------------------- 1 | { 2 | "onMove": { 3 | "entityId": { 4 | "option": "required", 5 | "type": "uInt32", 6 | "tag": 1 7 | }, 8 | "path": { 9 | "option": "repeated", 10 | "type": "Path", 11 | "tag": 2 12 | }, 13 | "speed": { 14 | "option": "required", 15 | "type": "uInt32", 16 | "tag": 3 17 | }, 18 | "__messages": { 19 | "Path": { 20 | "x": { 21 | "option": "required", 22 | "type": "uInt32", 23 | "tag": 1 24 | }, 25 | "y": { 26 | "option": "required", 27 | "type": "uInt32", 28 | "tag": 2 29 | }, 30 | "__messages": {}, 31 | "__tags": { 32 | "1": "x", 33 | "2": "y" 34 | } 35 | } 36 | }, 37 | "__tags": { 38 | "1": "entityId", 39 | "2": "path", 40 | "3": "speed" 41 | } 42 | }, 43 | "onAttack": { 44 | "attacker": { 45 | "option": "required", 46 | "type": "uInt32", 47 | "tag": 1 48 | }, 49 | "target": { 50 | "option": "required", 51 | "type": "uInt32", 52 | "tag": 2 53 | }, 54 | "result": { 55 | "option": "required", 56 | "type": "AttackResult", 57 | "tag": 3 58 | }, 59 | "exp": { 60 | "option": "optional", 61 | "type": "uInt32", 62 | "tag": 4 63 | }, 64 | "reviveTime": { 65 | "option": "optional", 66 | "type": "uInt32", 67 | "tag": 5 68 | }, 69 | "skillId": { 70 | "option": "required", 71 | "type": "uInt32", 72 | "tag": 6 73 | }, 74 | "__messages": { 75 | "AttackResult": { 76 | "result": { 77 | "option": "required", 78 | "type": "uInt32", 79 | "tag": 1 80 | }, 81 | "damage": { 82 | "option": "required", 83 | "type": "uInt32", 84 | "tag": 2 85 | }, 86 | "items": { 87 | "option": "repeated", 88 | "type": "Item", 89 | "tag": 4 90 | }, 91 | "__messages": { 92 | "Item": { 93 | "kindId": { 94 | "option": "required", 95 | "type": "uInt32", 96 | "tag": 1 97 | }, 98 | "x": { 99 | "option": "required", 100 | "type": "uInt32", 101 | "tag": 2 102 | }, 103 | "y": { 104 | "option": "required", 105 | "type": "uInt32", 106 | "tag": 3 107 | }, 108 | "entityId": { 109 | "option": "required", 110 | "type": "uInt32", 111 | "tag": 4 112 | }, 113 | "playerId": { 114 | "option": "required", 115 | "type": "uInt32", 116 | "tag": 5 117 | }, 118 | "type": { 119 | "option": "required", 120 | "type": "string", 121 | "tag": 6 122 | }, 123 | "__messages": {}, 124 | "__tags": { 125 | "1": "kindId", 126 | "2": "x", 127 | "3": "y", 128 | "4": "entityId", 129 | "5": "playerId", 130 | "6": "type" 131 | } 132 | } 133 | }, 134 | "__tags": { 135 | "1": "result", 136 | "2": "damage", 137 | "4": "items" 138 | } 139 | } 140 | }, 141 | "__tags": { 142 | "1": "attacker", 143 | "2": "target", 144 | "3": "result", 145 | "4": "exp", 146 | "5": "reviveTime", 147 | "6": "skillId" 148 | } 149 | }, 150 | "onUpgrade": { 151 | "entityId": { 152 | "option": "required", 153 | "type": "uInt32", 154 | "tag": 1 155 | }, 156 | "kindId": { 157 | "option": "required", 158 | "type": "uInt32", 159 | "tag": 2 160 | }, 161 | "x": { 162 | "option": "required", 163 | "type": "uInt32", 164 | "tag": 3 165 | }, 166 | "y": { 167 | "option": "required", 168 | "type": "uInt32", 169 | "tag": 4 170 | }, 171 | "level": { 172 | "option": "required", 173 | "type": "uInt32", 174 | "tag": 5 175 | }, 176 | "walkSpeed": { 177 | "option": "required", 178 | "type": "uInt32", 179 | "tag": 6 180 | }, 181 | "hp": { 182 | "option": "required", 183 | "type": "uInt32", 184 | "tag": 7 185 | }, 186 | "maxHp": { 187 | "option": "required", 188 | "type": "uInt32", 189 | "tag": 8 190 | }, 191 | "mp": { 192 | "option": "required", 193 | "type": "uInt32", 194 | "tag": 9 195 | }, 196 | "maxMp": { 197 | "option": "required", 198 | "type": "uInt32", 199 | "tag": 10 200 | }, 201 | "id": { 202 | "option": "required", 203 | "type": "uInt32", 204 | "tag": 11 205 | }, 206 | "name": { 207 | "option": "required", 208 | "type": "string", 209 | "tag": 12 210 | }, 211 | "experience": { 212 | "option": "required", 213 | "type": "uInt32", 214 | "tag": 13 215 | }, 216 | "attackValue": { 217 | "option": "required", 218 | "type": "uInt32", 219 | "tag": 14 220 | }, 221 | "defenceValue": { 222 | "option": "required", 223 | "type": "uInt32", 224 | "tag": 15 225 | }, 226 | "attackSpeed": { 227 | "option": "required", 228 | "type": "double", 229 | "tag": 16 230 | }, 231 | "areaId": { 232 | "option": "required", 233 | "type": "uInt32", 234 | "tag": 17 235 | }, 236 | "hitRate": { 237 | "option": "required", 238 | "type": "uInt32", 239 | "tag": 18 240 | }, 241 | "dodgeRate": { 242 | "option": "required", 243 | "type": "uInt32", 244 | "tag": 19 245 | }, 246 | "nextLevelExp": { 247 | "option": "required", 248 | "type": "uInt32", 249 | "tag": 20 250 | }, 251 | "skillPoint": { 252 | "option": "required", 253 | "type": "uInt32", 254 | "tag": 21 255 | }, 256 | "kindName": { 257 | "option": "required", 258 | "type": "string", 259 | "tag": 22 260 | }, 261 | "type": { 262 | "option": "required", 263 | "type": "string", 264 | "tag": 23 265 | }, 266 | "__messages": {}, 267 | "__tags": { 268 | "1": "entityId", 269 | "2": "kindId", 270 | "3": "x", 271 | "4": "y", 272 | "5": "level", 273 | "6": "walkSpeed", 274 | "7": "hp", 275 | "8": "maxHp", 276 | "9": "mp", 277 | "10": "maxMp", 278 | "11": "id", 279 | "12": "name", 280 | "13": "experience", 281 | "14": "attackValue", 282 | "15": "defenceValue", 283 | "16": "attackSpeed", 284 | "17": "areaId", 285 | "18": "hitRate", 286 | "19": "dodgeRate", 287 | "20": "nextLevelExp", 288 | "21": "skillPoint", 289 | "22": "kindName", 290 | "23": "type" 291 | } 292 | }, 293 | "onPickItem": { 294 | "player": { 295 | "option": "required", 296 | "type": "uInt32", 297 | "tag": 1 298 | }, 299 | "item": { 300 | "option": "required", 301 | "type": "uInt32", 302 | "tag": 2 303 | }, 304 | "index": { 305 | "option": "required", 306 | "type": "uInt32", 307 | "tag": 3 308 | }, 309 | "__messages": {}, 310 | "__tags": { 311 | "1": "player", 312 | "2": "item", 313 | "3": "index" 314 | } 315 | }, 316 | "onNPCTalk": { 317 | "npc": { 318 | "option": "required", 319 | "type": "uInt32", 320 | "tag": 1 321 | }, 322 | "npcword": { 323 | "option": "required", 324 | "type": "string", 325 | "tag": 2 326 | }, 327 | "myword": { 328 | "option": "required", 329 | "type": "string", 330 | "tag": 3 331 | }, 332 | "__messages": {}, 333 | "__tags": { 334 | "1": "npc", 335 | "2": "npcword", 336 | "3": "myword" 337 | } 338 | }, 339 | "onRevive": { 340 | "entityId": { 341 | "option": "required", 342 | "type": "uInt32", 343 | "tag": 1 344 | }, 345 | "x": { 346 | "option": "required", 347 | "type": "uInt32", 348 | "tag": 2 349 | }, 350 | "y": { 351 | "option": "required", 352 | "type": "uInt32", 353 | "tag": 3 354 | }, 355 | "hp": { 356 | "option": "required", 357 | "type": "uInt32", 358 | "tag": 4 359 | }, 360 | "__messages": {}, 361 | "__tags": { 362 | "1": "entityId", 363 | "2": "x", 364 | "3": "y", 365 | "4": "hp" 366 | } 367 | }, 368 | "onAddEntities": { 369 | "npc": { 370 | "option": "repeated", 371 | "type": "NPC", 372 | "tag": 1 373 | }, 374 | "mob": { 375 | "option": "repeated", 376 | "type": "Mob", 377 | "tag": 2 378 | }, 379 | "item": { 380 | "option": "repeated", 381 | "type": "Item", 382 | "tag": 3 383 | }, 384 | "euipment": { 385 | "option": "repeated", 386 | "type": "Equipment", 387 | "tag": 4 388 | }, 389 | "player": { 390 | "option": "repeated", 391 | "type": "Player", 392 | "tag": 5 393 | }, 394 | "__messages": { 395 | "NPC": { 396 | "entityId": { 397 | "option": "required", 398 | "type": "uInt32", 399 | "tag": 1 400 | }, 401 | "kindId": { 402 | "option": "required", 403 | "type": "uInt32", 404 | "tag": 2 405 | }, 406 | "x": { 407 | "option": "required", 408 | "type": "uInt32", 409 | "tag": 3 410 | }, 411 | "y": { 412 | "option": "required", 413 | "type": "uInt32", 414 | "tag": 4 415 | }, 416 | "__messages": {}, 417 | "__tags": { 418 | "1": "entityId", 419 | "2": "kindId", 420 | "3": "x", 421 | "4": "y" 422 | } 423 | }, 424 | "Mob": { 425 | "entityId": { 426 | "option": "required", 427 | "type": "uInt32", 428 | "tag": 1 429 | }, 430 | "kindId": { 431 | "option": "required", 432 | "type": "uInt32", 433 | "tag": 2 434 | }, 435 | "x": { 436 | "option": "required", 437 | "type": "uInt32", 438 | "tag": 3 439 | }, 440 | "y": { 441 | "option": "required", 442 | "type": "uInt32", 443 | "tag": 4 444 | }, 445 | "level": { 446 | "option": "required", 447 | "type": "uInt32", 448 | "tag": 5 449 | }, 450 | "walkSpeed": { 451 | "option": "required", 452 | "type": "uInt32", 453 | "tag": 6 454 | }, 455 | "hp": { 456 | "option": "required", 457 | "type": "uInt32", 458 | "tag": 7 459 | }, 460 | "maxHp": { 461 | "option": "required", 462 | "type": "uInt32", 463 | "tag": 8 464 | }, 465 | "__messages": {}, 466 | "__tags": { 467 | "1": "entityId", 468 | "2": "kindId", 469 | "3": "x", 470 | "4": "y", 471 | "5": "level", 472 | "6": "walkSpeed", 473 | "7": "hp", 474 | "8": "maxHp" 475 | } 476 | }, 477 | "Item": { 478 | "entityId": { 479 | "option": "required", 480 | "type": "uInt32", 481 | "tag": 1 482 | }, 483 | "kindId": { 484 | "option": "required", 485 | "type": "uInt32", 486 | "tag": 2 487 | }, 488 | "x": { 489 | "option": "required", 490 | "type": "uInt32", 491 | "tag": 3 492 | }, 493 | "y": { 494 | "option": "required", 495 | "type": "uInt32", 496 | "tag": 4 497 | }, 498 | "playerId": { 499 | "option": "required", 500 | "type": "uInt32", 501 | "tag": 5 502 | }, 503 | "__messages": {}, 504 | "__tags": { 505 | "1": "entityId", 506 | "2": "kindId", 507 | "3": "x", 508 | "4": "y", 509 | "5": "playerId" 510 | } 511 | }, 512 | "Equipment": { 513 | "entityId": { 514 | "option": "required", 515 | "type": "uInt32", 516 | "tag": 1 517 | }, 518 | "kindId": { 519 | "option": "required", 520 | "type": "uInt32", 521 | "tag": 2 522 | }, 523 | "x": { 524 | "option": "required", 525 | "type": "uInt32", 526 | "tag": 3 527 | }, 528 | "y": { 529 | "option": "required", 530 | "type": "uInt32", 531 | "tag": 4 532 | }, 533 | "playerId": { 534 | "option": "required", 535 | "type": "uInt32", 536 | "tag": 5 537 | }, 538 | "__messages": {}, 539 | "__tags": { 540 | "1": "entityId", 541 | "2": "kindId", 542 | "3": "x", 543 | "4": "y", 544 | "5": "playerId" 545 | } 546 | }, 547 | "Player": { 548 | "entityId": { 549 | "option": "required", 550 | "type": "uInt32", 551 | "tag": 1 552 | }, 553 | "kindId": { 554 | "option": "required", 555 | "type": "uInt32", 556 | "tag": 2 557 | }, 558 | "x": { 559 | "option": "required", 560 | "type": "uInt32", 561 | "tag": 3 562 | }, 563 | "y": { 564 | "option": "required", 565 | "type": "uInt32", 566 | "tag": 4 567 | }, 568 | "level": { 569 | "option": "required", 570 | "type": "uInt32", 571 | "tag": 5 572 | }, 573 | "walkSpeed": { 574 | "option": "required", 575 | "type": "uInt32", 576 | "tag": 6 577 | }, 578 | "hp": { 579 | "option": "required", 580 | "type": "uInt32", 581 | "tag": 7 582 | }, 583 | "maxHp": { 584 | "option": "required", 585 | "type": "uInt32", 586 | "tag": 8 587 | }, 588 | "mp": { 589 | "option": "required", 590 | "type": "uInt32", 591 | "tag": 9 592 | }, 593 | "maxMp": { 594 | "option": "required", 595 | "type": "uInt32", 596 | "tag": 10 597 | }, 598 | "id": { 599 | "option": "required", 600 | "type": "uInt32", 601 | "tag": 11 602 | }, 603 | "name": { 604 | "option": "required", 605 | "type": "string", 606 | "tag": 12 607 | }, 608 | "__messages": {}, 609 | "__tags": { 610 | "1": "entityId", 611 | "2": "kindId", 612 | "3": "x", 613 | "4": "y", 614 | "5": "level", 615 | "6": "walkSpeed", 616 | "7": "hp", 617 | "8": "maxHp", 618 | "9": "mp", 619 | "10": "maxMp", 620 | "11": "id", 621 | "12": "name" 622 | } 623 | } 624 | }, 625 | "__tags": { 626 | "1": "npc", 627 | "2": "mob", 628 | "3": "item", 629 | "4": "euipment", 630 | "5": "player" 631 | } 632 | }, 633 | "onRemoveEntities": { 634 | "entities": { 635 | "option": "repeated", 636 | "type": "uInt32", 637 | "tag": 1 638 | }, 639 | "__messages": {}, 640 | "__tags": { 641 | "1": "entities" 642 | } 643 | }, 644 | "onPathCheckOut": { 645 | "entityId": { 646 | "option": "required", 647 | "type": "uInt32", 648 | "tag": 1 649 | }, 650 | "position": { 651 | "option": "required", 652 | "type": "Position", 653 | "tag": 2 654 | }, 655 | "__messages": { 656 | "Position": { 657 | "x": { 658 | "option": "required", 659 | "type": "uInt32", 660 | "tag": 1 661 | }, 662 | "y": { 663 | "option": "required", 664 | "type": "uInt32", 665 | "tag": 2 666 | }, 667 | "__messages": {}, 668 | "__tags": { 669 | "1": "x", 670 | "2": "y" 671 | } 672 | } 673 | }, 674 | "__tags": { 675 | "1": "entityId", 676 | "2": "position" 677 | } 678 | }, 679 | "area.playerHandler.enterScene": { 680 | "entities": { 681 | "option": "optional", 682 | "type": "Entities", 683 | "tag": 1 684 | }, 685 | "curPlayer": { 686 | "option": "optional", 687 | "type": "Player", 688 | "tag": 2 689 | }, 690 | "map": { 691 | "option": "required", 692 | "type": "Map", 693 | "tag": 3 694 | }, 695 | "__messages": { 696 | "Entities": { 697 | "npc": { 698 | "option": "repeated", 699 | "type": "NPC", 700 | "tag": 1 701 | }, 702 | "mob": { 703 | "option": "repeated", 704 | "type": "Mob", 705 | "tag": 2 706 | }, 707 | "item": { 708 | "option": "repeated", 709 | "type": "Item", 710 | "tag": 3 711 | }, 712 | "euipment": { 713 | "option": "repeated", 714 | "type": "Equipment", 715 | "tag": 4 716 | }, 717 | "player": { 718 | "option": "repeated", 719 | "type": "Player", 720 | "tag": 5 721 | }, 722 | "__messages": { 723 | "NPC": { 724 | "entityId": { 725 | "option": "required", 726 | "type": "uInt32", 727 | "tag": 1 728 | }, 729 | "kindId": { 730 | "option": "required", 731 | "type": "uInt32", 732 | "tag": 2 733 | }, 734 | "x": { 735 | "option": "required", 736 | "type": "uInt32", 737 | "tag": 3 738 | }, 739 | "y": { 740 | "option": "required", 741 | "type": "uInt32", 742 | "tag": 4 743 | }, 744 | "__messages": {}, 745 | "__tags": { 746 | "1": "entityId", 747 | "2": "kindId", 748 | "3": "x", 749 | "4": "y" 750 | } 751 | }, 752 | "Mob": { 753 | "entityId": { 754 | "option": "required", 755 | "type": "uInt32", 756 | "tag": 1 757 | }, 758 | "kindId": { 759 | "option": "required", 760 | "type": "uInt32", 761 | "tag": 2 762 | }, 763 | "x": { 764 | "option": "required", 765 | "type": "uInt32", 766 | "tag": 3 767 | }, 768 | "y": { 769 | "option": "required", 770 | "type": "uInt32", 771 | "tag": 4 772 | }, 773 | "level": { 774 | "option": "required", 775 | "type": "uInt32", 776 | "tag": 5 777 | }, 778 | "walkSpeed": { 779 | "option": "required", 780 | "type": "uInt32", 781 | "tag": 6 782 | }, 783 | "hp": { 784 | "option": "required", 785 | "type": "uInt32", 786 | "tag": 7 787 | }, 788 | "maxHp": { 789 | "option": "required", 790 | "type": "uInt32", 791 | "tag": 8 792 | }, 793 | "__messages": {}, 794 | "__tags": { 795 | "1": "entityId", 796 | "2": "kindId", 797 | "3": "x", 798 | "4": "y", 799 | "5": "level", 800 | "6": "walkSpeed", 801 | "7": "hp", 802 | "8": "maxHp" 803 | } 804 | }, 805 | "Item": { 806 | "entityId": { 807 | "option": "required", 808 | "type": "uInt32", 809 | "tag": 1 810 | }, 811 | "kindId": { 812 | "option": "required", 813 | "type": "uInt32", 814 | "tag": 2 815 | }, 816 | "x": { 817 | "option": "required", 818 | "type": "uInt32", 819 | "tag": 3 820 | }, 821 | "y": { 822 | "option": "required", 823 | "type": "uInt32", 824 | "tag": 4 825 | }, 826 | "playerId": { 827 | "option": "required", 828 | "type": "uInt32", 829 | "tag": 5 830 | }, 831 | "__messages": {}, 832 | "__tags": { 833 | "1": "entityId", 834 | "2": "kindId", 835 | "3": "x", 836 | "4": "y", 837 | "5": "playerId" 838 | } 839 | }, 840 | "Equipment": { 841 | "entityId": { 842 | "option": "required", 843 | "type": "uInt32", 844 | "tag": 1 845 | }, 846 | "kindId": { 847 | "option": "required", 848 | "type": "uInt32", 849 | "tag": 2 850 | }, 851 | "x": { 852 | "option": "required", 853 | "type": "uInt32", 854 | "tag": 3 855 | }, 856 | "y": { 857 | "option": "required", 858 | "type": "uInt32", 859 | "tag": 4 860 | }, 861 | "playerId": { 862 | "option": "required", 863 | "type": "uInt32", 864 | "tag": 5 865 | }, 866 | "__messages": {}, 867 | "__tags": { 868 | "1": "entityId", 869 | "2": "kindId", 870 | "3": "x", 871 | "4": "y", 872 | "5": "playerId" 873 | } 874 | }, 875 | "Player": { 876 | "entityId": { 877 | "option": "required", 878 | "type": "uInt32", 879 | "tag": 1 880 | }, 881 | "kindId": { 882 | "option": "required", 883 | "type": "uInt32", 884 | "tag": 2 885 | }, 886 | "x": { 887 | "option": "required", 888 | "type": "uInt32", 889 | "tag": 3 890 | }, 891 | "y": { 892 | "option": "required", 893 | "type": "uInt32", 894 | "tag": 4 895 | }, 896 | "level": { 897 | "option": "required", 898 | "type": "uInt32", 899 | "tag": 5 900 | }, 901 | "walkSpeed": { 902 | "option": "required", 903 | "type": "uInt32", 904 | "tag": 6 905 | }, 906 | "hp": { 907 | "option": "required", 908 | "type": "uInt32", 909 | "tag": 7 910 | }, 911 | "maxHp": { 912 | "option": "required", 913 | "type": "uInt32", 914 | "tag": 8 915 | }, 916 | "mp": { 917 | "option": "required", 918 | "type": "uInt32", 919 | "tag": 9 920 | }, 921 | "maxMp": { 922 | "option": "required", 923 | "type": "uInt32", 924 | "tag": 10 925 | }, 926 | "id": { 927 | "option": "required", 928 | "type": "uInt32", 929 | "tag": 11 930 | }, 931 | "name": { 932 | "option": "required", 933 | "type": "string", 934 | "tag": 12 935 | }, 936 | "__messages": {}, 937 | "__tags": { 938 | "1": "entityId", 939 | "2": "kindId", 940 | "3": "x", 941 | "4": "y", 942 | "5": "level", 943 | "6": "walkSpeed", 944 | "7": "hp", 945 | "8": "maxHp", 946 | "9": "mp", 947 | "10": "maxMp", 948 | "11": "id", 949 | "12": "name" 950 | } 951 | } 952 | }, 953 | "__tags": { 954 | "1": "npc", 955 | "2": "mob", 956 | "3": "item", 957 | "4": "euipment", 958 | "5": "player" 959 | } 960 | }, 961 | "Player": { 962 | "entityId": { 963 | "option": "required", 964 | "type": "uInt32", 965 | "tag": 1 966 | }, 967 | "kindId": { 968 | "option": "required", 969 | "type": "uInt32", 970 | "tag": 2 971 | }, 972 | "x": { 973 | "option": "required", 974 | "type": "uInt32", 975 | "tag": 3 976 | }, 977 | "y": { 978 | "option": "required", 979 | "type": "uInt32", 980 | "tag": 4 981 | }, 982 | "level": { 983 | "option": "required", 984 | "type": "uInt32", 985 | "tag": 5 986 | }, 987 | "walkSpeed": { 988 | "option": "required", 989 | "type": "uInt32", 990 | "tag": 6 991 | }, 992 | "hp": { 993 | "option": "required", 994 | "type": "uInt32", 995 | "tag": 7 996 | }, 997 | "maxHp": { 998 | "option": "required", 999 | "type": "uInt32", 1000 | "tag": 8 1001 | }, 1002 | "mp": { 1003 | "option": "required", 1004 | "type": "uInt32", 1005 | "tag": 9 1006 | }, 1007 | "maxMp": { 1008 | "option": "required", 1009 | "type": "uInt32", 1010 | "tag": 10 1011 | }, 1012 | "id": { 1013 | "option": "required", 1014 | "type": "uInt32", 1015 | "tag": 11 1016 | }, 1017 | "name": { 1018 | "option": "required", 1019 | "type": "string", 1020 | "tag": 12 1021 | }, 1022 | "experience": { 1023 | "option": "required", 1024 | "type": "uInt32", 1025 | "tag": 13 1026 | }, 1027 | "attackValue": { 1028 | "option": "required", 1029 | "type": "uInt32", 1030 | "tag": 14 1031 | }, 1032 | "defenceValue": { 1033 | "option": "required", 1034 | "type": "uInt32", 1035 | "tag": 15 1036 | }, 1037 | "attackSpeed": { 1038 | "option": "required", 1039 | "type": "double", 1040 | "tag": 16 1041 | }, 1042 | "areaId": { 1043 | "option": "required", 1044 | "type": "uInt32", 1045 | "tag": 17 1046 | }, 1047 | "hitRate": { 1048 | "option": "required", 1049 | "type": "uInt32", 1050 | "tag": 18 1051 | }, 1052 | "dodgeRate": { 1053 | "option": "required", 1054 | "type": "uInt32", 1055 | "tag": 19 1056 | }, 1057 | "nextLevelExp": { 1058 | "option": "required", 1059 | "type": "uInt32", 1060 | "tag": 20 1061 | }, 1062 | "skillPoint": { 1063 | "option": "required", 1064 | "type": "uInt32", 1065 | "tag": 21 1066 | }, 1067 | "type": { 1068 | "option": "required", 1069 | "type": "string", 1070 | "tag": 22 1071 | }, 1072 | "bag": { 1073 | "option": "required", 1074 | "type": "Bag", 1075 | "tag": 23 1076 | }, 1077 | "equipments": { 1078 | "option": "required", 1079 | "type": "Equipments", 1080 | "tag": 24 1081 | }, 1082 | "fightSkills": { 1083 | "option": "repeated", 1084 | "type": "FightSkill", 1085 | "tag": 25 1086 | }, 1087 | "__messages": { 1088 | "Bag": { 1089 | "itemCount": { 1090 | "option": "required", 1091 | "type": "uInt32", 1092 | "tag": 1 1093 | }, 1094 | "items": { 1095 | "option": "repeated", 1096 | "type": "Item", 1097 | "tag": 2 1098 | }, 1099 | "__messages": { 1100 | "Item": { 1101 | "key": { 1102 | "option": "required", 1103 | "type": "uInt32", 1104 | "tag": 1 1105 | }, 1106 | "id": { 1107 | "option": "required", 1108 | "type": "uInt32", 1109 | "tag": 2 1110 | }, 1111 | "type": { 1112 | "option": "required", 1113 | "type": "string", 1114 | "tag": 3 1115 | }, 1116 | "__messages": {}, 1117 | "__tags": { 1118 | "1": "key", 1119 | "2": "id", 1120 | "3": "type" 1121 | } 1122 | } 1123 | }, 1124 | "__tags": { 1125 | "1": "itemCount", 1126 | "2": "items" 1127 | } 1128 | }, 1129 | "Equipments": { 1130 | "weapon": { 1131 | "option": "required", 1132 | "type": "uInt32", 1133 | "tag": 1 1134 | }, 1135 | "armor": { 1136 | "option": "required", 1137 | "type": "uInt32", 1138 | "tag": 2 1139 | }, 1140 | "helmet": { 1141 | "option": "required", 1142 | "type": "uInt32", 1143 | "tag": 3 1144 | }, 1145 | "necklace": { 1146 | "option": "required", 1147 | "type": "uInt32", 1148 | "tag": 4 1149 | }, 1150 | "ring": { 1151 | "option": "required", 1152 | "type": "uInt32", 1153 | "tag": 5 1154 | }, 1155 | "belt": { 1156 | "option": "required", 1157 | "type": "uInt32", 1158 | "tag": 6 1159 | }, 1160 | "shoes": { 1161 | "option": "required", 1162 | "type": "uInt32", 1163 | "tag": 7 1164 | }, 1165 | "legguard": { 1166 | "option": "required", 1167 | "type": "uInt32", 1168 | "tag": 8 1169 | }, 1170 | "amulet": { 1171 | "option": "required", 1172 | "type": "uInt32", 1173 | "tag": 9 1174 | }, 1175 | "__messages": {}, 1176 | "__tags": { 1177 | "1": "weapon", 1178 | "2": "armor", 1179 | "3": "helmet", 1180 | "4": "necklace", 1181 | "5": "ring", 1182 | "6": "belt", 1183 | "7": "shoes", 1184 | "8": "legguard", 1185 | "9": "amulet" 1186 | } 1187 | }, 1188 | "FightSkill": { 1189 | "id": { 1190 | "option": "required", 1191 | "type": "uInt32", 1192 | "tag": 1 1193 | }, 1194 | "level": { 1195 | "option": "required", 1196 | "type": "uInt32", 1197 | "tag": 2 1198 | }, 1199 | "__messages": {}, 1200 | "__tags": { 1201 | "1": "id", 1202 | "2": "level" 1203 | } 1204 | } 1205 | }, 1206 | "__tags": { 1207 | "1": "entityId", 1208 | "2": "kindId", 1209 | "3": "x", 1210 | "4": "y", 1211 | "5": "level", 1212 | "6": "walkSpeed", 1213 | "7": "hp", 1214 | "8": "maxHp", 1215 | "9": "mp", 1216 | "10": "maxMp", 1217 | "11": "id", 1218 | "12": "name", 1219 | "13": "experience", 1220 | "14": "attackValue", 1221 | "15": "defenceValue", 1222 | "16": "attackSpeed", 1223 | "17": "areaId", 1224 | "18": "hitRate", 1225 | "19": "dodgeRate", 1226 | "20": "nextLevelExp", 1227 | "21": "skillPoint", 1228 | "22": "type", 1229 | "23": "bag", 1230 | "24": "equipments", 1231 | "25": "fightSkills" 1232 | } 1233 | }, 1234 | "Map": { 1235 | "name": { 1236 | "option": "required", 1237 | "type": "string", 1238 | "tag": 1 1239 | }, 1240 | "width": { 1241 | "option": "required", 1242 | "type": "uInt32", 1243 | "tag": 2 1244 | }, 1245 | "height": { 1246 | "option": "required", 1247 | "type": "uInt32", 1248 | "tag": 3 1249 | }, 1250 | "tileW": { 1251 | "option": "required", 1252 | "type": "uInt32", 1253 | "tag": 4 1254 | }, 1255 | "tileH": { 1256 | "option": "required", 1257 | "type": "uInt32", 1258 | "tag": 5 1259 | }, 1260 | "weightMap": { 1261 | "option": "repeated", 1262 | "type": "Collisions", 1263 | "tag": 6 1264 | }, 1265 | "__messages": { 1266 | "Collisions": { 1267 | "collisions": { 1268 | "option": "repeated", 1269 | "type": "Collison", 1270 | "tag": 1 1271 | }, 1272 | "__messages": { 1273 | "Collison": { 1274 | "start": { 1275 | "option": "required", 1276 | "type": "uInt32", 1277 | "tag": 1 1278 | }, 1279 | "length": { 1280 | "option": "required", 1281 | "type": "uInt32", 1282 | "tag": 2 1283 | }, 1284 | "__messages": {}, 1285 | "__tags": { 1286 | "1": "start", 1287 | "2": "length" 1288 | } 1289 | } 1290 | }, 1291 | "__tags": { 1292 | "1": "collisions" 1293 | } 1294 | } 1295 | }, 1296 | "__tags": { 1297 | "1": "name", 1298 | "2": "width", 1299 | "3": "height", 1300 | "4": "tileW", 1301 | "5": "tileH", 1302 | "6": "weightMap" 1303 | } 1304 | } 1305 | }, 1306 | "__tags": { 1307 | "1": "entities", 1308 | "2": "curPlayer", 1309 | "3": "map" 1310 | } 1311 | } 1312 | } -------------------------------------------------------------------------------- /test/rootMsg.json: -------------------------------------------------------------------------------- 1 | { 2 | "onMove": { 3 | "entityId": 14, 4 | "path": [ 5 | { 6 | "x": 128, 7 | "y": 796 8 | }, 9 | { 10 | "x": 677, 11 | "y": 895 12 | } 13 | ], 14 | "speed": 160 15 | }, 16 | "area.playerHandler.enterScene": { 17 | "entities": { 18 | "item": [ 19 | { 20 | "entityId": 1, 21 | "kindId": 1001 22 | }, 23 | { 24 | "entityId": 2, 25 | "kindId": 1002 26 | } 27 | ], 28 | "equipment": [ 29 | { 30 | "entityId": 1, 31 | "kindId": 2001 32 | }, 33 | { 34 | "entityId": 2, 35 | "kindId": 2002 36 | } 37 | ] 38 | }, 39 | "curPlayer": { 40 | "entityId": 1, 41 | "kindId": 3001, 42 | "bag": { 43 | "items": [ 44 | { 45 | "id": 1, 46 | "type": "pomelo" 47 | }, 48 | { 49 | "id": 2, 50 | "type": "protobuf" 51 | } 52 | ] 53 | }, 54 | "equipments": [ 55 | { 56 | "entityId": 1, 57 | "kindId": 2001 58 | }, 59 | { 60 | "entityId": 2, 61 | "kindId": 2002 62 | } 63 | ] 64 | }, 65 | "map": { 66 | "name": "a", 67 | "width": 10, 68 | "height": 10, 69 | "tileW": 5, 70 | "tileH": 5, 71 | "weightMap": [ 72 | { 73 | "collisions": [] 74 | }, 75 | { 76 | "collisions": [] 77 | }, 78 | { 79 | "collisions": [ 80 | { 81 | "start": 1, 82 | "length": 3 83 | }, 84 | { 85 | "start": 79, 86 | "length": 3 87 | } 88 | ] 89 | }, 90 | { 91 | "collisions": [ 92 | { 93 | "start": 27, 94 | "length": 2 95 | }, 96 | { 97 | "start": 78, 98 | "length": 4 99 | } 100 | ] 101 | } 102 | ] 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /test/rootMsgTC.js: -------------------------------------------------------------------------------- 1 | var tc = module.exports; 2 | 3 | tc.onMove = { 4 | 'entityId':14, 5 | 'path' : [{'x':128,'y':796},{'x':677,'y':895}], 6 | 'speed':160 7 | }; 8 | 9 | tc['area.playerHandler.enterScene'] = { 10 | entities : { 11 | item : [ 12 | { 13 | entityId : 1, 14 | kindId : 1001 15 | }, 16 | { 17 | entityId : 2, 18 | kindId : 1002 19 | } 20 | ], 21 | equipment : [ 22 | { 23 | entityId : 1, 24 | kindId : 2001 25 | }, 26 | { 27 | entityId : 2, 28 | kindId : 2002 29 | } 30 | ] 31 | }, 32 | curPlayer : { 33 | entityId : 1, 34 | kindId : 3001, 35 | bag : { 36 | items : [ 37 | { 38 | id : 1, 39 | type : 'pomelo' 40 | }, 41 | { 42 | id : 2, 43 | type : 'protobuf' 44 | } 45 | ] 46 | }, 47 | equipments : [ 48 | { 49 | entityId : 1, 50 | kindId : 2001 51 | }, 52 | { 53 | entityId : 2, 54 | kindId : 2002 55 | } 56 | ] 57 | }, 58 | map : { 59 | name : 'a', 60 | width : 10, 61 | height : 10, 62 | tileW : 5, 63 | tileH : 5, 64 | weightMap:[ 65 | {'collisions':[]}, 66 | {'collisions':[]}, 67 | {'collisions':[ 68 | {'start':1,'length':3}, 69 | {'start':79,'length':3} 70 | ]}, 71 | {'collisions':[ 72 | {'start':27,'length':2}, 73 | {'start':78,'length':4} 74 | ]} 75 | ] 76 | } 77 | }; -------------------------------------------------------------------------------- /test/rootMsgTest.js: -------------------------------------------------------------------------------- 1 | var protobuf = require('../lib/protobuf'); 2 | var util = require('../lib/util'); 3 | var should = require('should'); 4 | var tc = require('./rootMsgTC'); 5 | 6 | describe('msgEncoderTest', function(){ 7 | var protos = protobuf.parse(require('./rootMsg.json')); 8 | // console.log(protos); 9 | 10 | protobuf.init({encoderProtos:protos, decoderProtos:protos}); 11 | 12 | describe('encodeTest', function(){ 13 | // console.log('%j', tc); 14 | 15 | for(var route in tc){ 16 | var msg = tc[route]; 17 | 18 | console.log('===================='); 19 | console.log(route); 20 | var buffer = protobuf.encode(route, msg); 21 | 22 | console.log(msg); 23 | console.log(buffer.length); 24 | // console.log(buffer); 25 | 26 | var decodeMsg = protobuf.decode(route, buffer); 27 | 28 | console.log(decodeMsg); 29 | console.log('===================='); 30 | 31 | util.equal(msg, decodeMsg).should.equal(true); 32 | } 33 | }); 34 | }); -------------------------------------------------------------------------------- /test/rootProtos.json: -------------------------------------------------------------------------------- 1 | { 2 | "message Path": { 3 | "x": { 4 | "option": "required", 5 | "type": "double", 6 | "tag": 1 7 | }, 8 | "y": { 9 | "option": "required", 10 | "type": "double", 11 | "tag": 2 12 | }, 13 | "__messages": {}, 14 | "__tags": { 15 | "1": "x", 16 | "2": "y" 17 | } 18 | }, 19 | "message Map": { 20 | "name": { 21 | "option": "required", 22 | "type": "string", 23 | "tag": 1 24 | }, 25 | "width": { 26 | "option": "required", 27 | "type": "uInt32", 28 | "tag": 2 29 | }, 30 | "height": { 31 | "option": "required", 32 | "type": "uInt32", 33 | "tag": 3 34 | }, 35 | "tileW": { 36 | "option": "required", 37 | "type": "uInt32", 38 | "tag": 4 39 | }, 40 | "tileH": { 41 | "option": "required", 42 | "type": "uInt32", 43 | "tag": 5 44 | }, 45 | "weightMap": { 46 | "option": "repeated", 47 | "type": "Collisions", 48 | "tag": 6 49 | }, 50 | "__messages": { 51 | "Collisions": { 52 | "collisions": { 53 | "option": "repeated", 54 | "type": "Collison", 55 | "tag": 1 56 | }, 57 | "__messages": { 58 | "Collison": { 59 | "start": { 60 | "option": "required", 61 | "type": "uInt32", 62 | "tag": 1 63 | }, 64 | "length": { 65 | "option": "required", 66 | "type": "uInt32", 67 | "tag": 2 68 | }, 69 | "__messages": {}, 70 | "__tags": { 71 | "1": "start", 72 | "2": "length" 73 | } 74 | } 75 | }, 76 | "__tags": { 77 | "1": "collisions" 78 | } 79 | } 80 | }, 81 | "__tags": { 82 | "1": "name", 83 | "2": "width", 84 | "3": "height", 85 | "4": "tileW", 86 | "5": "tileH", 87 | "6": "weightMap" 88 | } 89 | }, 90 | "message Item": { 91 | "entityId": { 92 | "option": "required", 93 | "type": "uInt32", 94 | "tag": 1 95 | }, 96 | "kindId": { 97 | "option": "required", 98 | "type": "uInt32", 99 | "tag": 2 100 | }, 101 | "__messages": {}, 102 | "__tags": { 103 | "1": "entityId", 104 | "2": "kindId" 105 | } 106 | }, 107 | "message Equipment": { 108 | "entityId": { 109 | "option": "required", 110 | "type": "uInt32", 111 | "tag": 1 112 | }, 113 | "kindId": { 114 | "option": "required", 115 | "type": "uInt32", 116 | "tag": 2 117 | }, 118 | "__messages": {}, 119 | "__tags": { 120 | "1": "entityId", 121 | "2": "kindId" 122 | } 123 | }, 124 | "message Entities": { 125 | "item": { 126 | "option": "repeated", 127 | "type": "Item", 128 | "tag": 1 129 | }, 130 | "equipment": { 131 | "option": "repeated", 132 | "type": "Equipment", 133 | "tag": 2 134 | }, 135 | "__messages": {}, 136 | "__tags": { 137 | "1": "item", 138 | "2": "equipment" 139 | } 140 | }, 141 | "onMove": { 142 | "entityId": { 143 | "option": "required", 144 | "type": "uInt32", 145 | "tag": 1 146 | }, 147 | "path": { 148 | "option": "repeated", 149 | "type": "Path", 150 | "tag": 2 151 | }, 152 | "speed": { 153 | "option": "required", 154 | "type": "float", 155 | "tag": 3 156 | }, 157 | "__messages": {}, 158 | "__tags": { 159 | "1": "entityId", 160 | "2": "path", 161 | "3": "speed" 162 | } 163 | }, 164 | "area.playerHandler.enterScene": { 165 | "entities": { 166 | "option": "optional", 167 | "type": "Entities", 168 | "tag": 1 169 | }, 170 | "curPlayer": { 171 | "option": "optional", 172 | "type": "Player", 173 | "tag": 2 174 | }, 175 | "map": { 176 | "option": "required", 177 | "type": "Map", 178 | "tag": 3 179 | }, 180 | "__messages": { 181 | "Player": { 182 | "entityId": { 183 | "option": "required", 184 | "type": "uInt32", 185 | "tag": 1 186 | }, 187 | "kindId": { 188 | "option": "required", 189 | "type": "uInt32", 190 | "tag": 2 191 | }, 192 | "bag": { 193 | "option": "required", 194 | "type": "Bag", 195 | "tag": 3 196 | }, 197 | "equipments": { 198 | "option": "repeated", 199 | "type": "Equipment", 200 | "tag": 4 201 | }, 202 | "__messages": { 203 | "Bag": { 204 | "items": { 205 | "option": "repeated", 206 | "type": "Item", 207 | "tag": 1 208 | }, 209 | "__messages": { 210 | "Item": { 211 | "id": { 212 | "option": "required", 213 | "type": "uInt32", 214 | "tag": 1 215 | }, 216 | "type": { 217 | "option": "optional", 218 | "type": "string", 219 | "tag": 2 220 | }, 221 | "__messages": {}, 222 | "__tags": { 223 | "1": "id", 224 | "2": "type" 225 | } 226 | } 227 | }, 228 | "__tags": { 229 | "1": "items" 230 | } 231 | } 232 | }, 233 | "__tags": { 234 | "1": "entityId", 235 | "2": "kindId", 236 | "3": "bag", 237 | "4": "equipments" 238 | } 239 | } 240 | }, 241 | "__tags": { 242 | "1": "entities", 243 | "2": "curPlayer", 244 | "3": "map" 245 | } 246 | } 247 | } -------------------------------------------------------------------------------- /test/stringBufferTest.js: -------------------------------------------------------------------------------- 1 | 2 | function joinTest(num){ 3 | 4 | var arr = []; 5 | for(var i = 0; i < num; i++) 6 | arr.push(i + ''); 7 | 8 | var start = Date.now(); 9 | var str = ''; 10 | 11 | for(var i = 0; i < num; i++){ 12 | str += arr[i]; 13 | } 14 | 15 | var end = Date.now(); 16 | var time1 = end - start; 17 | 18 | start = Date.now(); 19 | var arr = []; 20 | for(var i = 0; i < num; i++){ 21 | arr.push(arr[i]); 22 | } 23 | var str1 = arr.join(); 24 | end = Date.now(); 25 | var time2 = end - start; 26 | 27 | console.log('test count : %j, \ncost 1 : %j, \ncost 2 : %j', num, time1, time2); 28 | } 29 | 30 | joinTest(100); 31 | joinTest(50000); 32 | joinTest(100000); 33 | joinTest(200000); 34 | //joinTest(500000); -------------------------------------------------------------------------------- /test/testMsg.js: -------------------------------------------------------------------------------- 1 | var tc = module.exports; 2 | 3 | tc['area.playerHandler.enterScene'] = { 4 | map : { 5 | name : 'a', 6 | width : 10, 7 | height : 10, 8 | tileW : 5, 9 | tileH : 5, 10 | weightMap:[ 11 | {'collisions':[]}, 12 | {'collisions':[]}, 13 | {'collisions':[ 14 | {'start':1,'length':3}, 15 | {'start':79,'length':3} 16 | ]}, 17 | {'collisions':[ 18 | {'start':27,'length':2}, 19 | {'start':78,'length':4} 20 | ]} 21 | ] 22 | } 23 | }; 24 | 25 | tc.onMove = { 26 | 'entityId':14, 27 | 'path' : [{'x':128,'y':796},{'x':677,'y':895}], 28 | 'speed':160 29 | }; 30 | 31 | tc.onUpgrade = { 32 | id:32726, 33 | entityId:48, 34 | name:'super1', 35 | kindId:210, 36 | kindName:'Angle', 37 | type:'player', 38 | x:755, 39 | y:608, 40 | hp:352, 41 | mp:32, 42 | maxHp:352, 43 | maxMp:32, 44 | level:3, 45 | experience:2, 46 | attackValue:37, 47 | defenceValue:15, 48 | walkSpeed:240, 49 | attackSpeed:1.2, 50 | areaId:1, 51 | hitRate:90, 52 | dodgeRate:13, 53 | nextLevelExp:114, 54 | skillPoint:3 55 | }; 56 | -------------------------------------------------------------------------------- /test/writeProtos.js: -------------------------------------------------------------------------------- 1 | var protobuf = require('../lib/protobuf'); 2 | var fs = require('fs'); 3 | 4 | var protoFile = "./rootMsg.json"; 5 | var protoTarget = "./rootProtos.json"; 6 | var msgFile = "./rootMsgTC"; 7 | var msgTarget = "./rootMsg.json"; 8 | 9 | var protos = protobuf.parse(require(protoFile)); 10 | 11 | console.log(protos); 12 | fs.writeFile(protoTarget, JSON.stringify(protos, null ,2)); 13 | 14 | fs.writeFile(msgTarget, JSON.stringify(require(msgFile), null ,2)); 15 | 16 | --------------------------------------------------------------------------------