UNPKG

occaecatidicta

Version:
193 lines (165 loc) 7.05 kB
import * as codec from './codec'; import * as constant from './constant'; import * as util from './util'; import { checkMsgValid } from './util'; export class Encoder { protos: any; private readonly _encodeCache: Buffer; constructor(protos: any, encoderCacheSize?: number) { this.init(protos); if (encoderCacheSize) { this._encodeCache = Buffer.alloc(encoderCacheSize); } } init(protos: any) { this.protos = protos || {}; } encode(route: string, msg: { [key: string]: any }) { if (!route || !msg) { console.warn('Route or msg can not be null! route : %j, msg %j', route, msg); return null; } // Get protos from protos map use the route as key let protos = this.protos[route]; // Check msg if (!this.checkMsg(msg, protos)) { console.error('check msg failed! msg : %j, proto : %j', msg, protos); return null; } let buffer = this._encodeCache; if (!buffer) { // Set the length of the buffer 2 times bigger to prevent overflow let length = Buffer.byteLength(JSON.stringify(msg)) * 2; // Init buffer and offset buffer = Buffer.alloc(length); } let offset = 0; if (!!protos) { offset = this.encodeMsg(buffer, offset, protos, msg); if (offset > 0) { return buffer.slice(0, offset); } } return null; } /** * Check if the msg follow the defination in the protos */ checkMsg(msg: { [key: string]: any }, protos: { [key: string]: any }) { return checkMsgValid(msg, protos, this.protos) } encodeMsg(buffer: Buffer, offset: number, protos: { [key: string]: any }, msg: { [key: string]: any }) { for (let name in msg) { if (!!protos[name]) { let proto = protos[name]; switch (proto.option) { case 'required': case 'optional': offset = this.writeBytes(buffer, offset, this.encodeTag(proto.type, proto.tag)); offset = this.encodeProp(msg[name], proto.type, offset, buffer, protos); break; case 'repeated': if (!!msg[name] && msg[name].length > 0) { offset = this.encodeArray(msg[name], proto, offset, buffer, protos); } break; } } } return offset; } encodeProp(value: any, type: string, offset: number, buffer: Buffer, protos?: { [key: string]: any }) { let length = 0; switch (type) { case 'uInt32': offset = this.writeBytes(buffer, offset, codec.encodeUInt32(value)); break; case 'int32': case 'sInt32': offset = this.writeBytes(buffer, offset, codec.encodeSInt32(value)); break; case 'float': buffer.writeFloatLE(value, offset); offset += 4; break; case 'double': buffer.writeDoubleLE(value, offset); offset += 8; break; case 'string': length = Buffer.byteLength(value); // Encode length offset = this.writeBytes(buffer, offset, codec.encodeUInt32(length)); // write string buffer.write(value, offset, length); offset += length; break; default: let message: { [key: string]: any } = protos.__messages[type] || this.protos['message ' + type]; if (!!message) { if (this._encodeCache) { let lengthOffset = offset; // 先预留1字节的长度位置 一般的消息都是小于128字节的. // 大于128字节就copy数据. 原来的逻辑本来就需要copy所以对性能只有提升,没有降低 offset += 1; offset = this.encodeMsg(buffer, offset, message, value); let msgLength = offset - lengthOffset - 1; let lenBytes = codec.encodeUInt32(msgLength); if (lenBytes.length === 1) { buffer[lengthOffset] = lenBytes[0]; } else { let moveSize = lenBytes.length - 1; offset += moveSize for (let i = offset - 1; i >= lengthOffset + 1; i--) { buffer[i] = buffer[i - moveSize]; } this.writeBytes(buffer, lengthOffset, lenBytes); } break; } // Use a tmp buffer to build an internal msg let tmpBuffer = Buffer.alloc(Buffer.byteLength(JSON.stringify(value)) * 2); length = 0; length = this.encodeMsg(tmpBuffer, length, message, value); // Encode length offset = this.writeBytes(buffer, offset, codec.encodeUInt32(length)); // contact the object tmpBuffer.copy(buffer, offset, 0, length); offset += length; } break; } return offset; } /** * Encode reapeated properties, simple msg and object are decode differented */ encodeArray(array: Array<number>, proto: { [key: string]: any }, offset: number, buffer: Buffer, protos: { [key: string]: any }) { let i = 0; if (util.isSimpleType(proto.type)) { offset = this.writeBytes(buffer, offset, this.encodeTag(proto.type, proto.tag)); offset = this.writeBytes(buffer, offset, codec.encodeUInt32(array.length)); for (i = 0; i < array.length; i++) { offset = this.encodeProp(array[i], proto.type, offset, buffer); } } else { for (i = 0; i < array.length; i++) { offset = this.writeBytes(buffer, offset, this.encodeTag(proto.type, proto.tag)); offset = this.encodeProp(array[i], proto.type, offset, buffer, protos); } } return offset; } writeBytes(buffer: Buffer, offset: number, bytes: Array<number>) { for (let i = 0; i < bytes.length; i++) { buffer.writeUInt8(bytes[i], offset); offset++; } return offset; } encodeTag(type: keyof typeof constant.TYPES, tag: number) { let value = constant.TYPES[type]; if (value === undefined) value = 2; return codec.encodeUInt32((tag << 3) | value); } }