当前位置:网站首页>When creator meets protobufjs 𞓜

When creator meets protobufjs 𞓜

2021-08-08 15:41:47 wx5d33905354cb6

Through the previous two articles, we explored how to creator Use in protobuf, And make it work in the browser 、JSB On , Finally protobuf stay js Some pain points used in the project . In this blog post, I want to open these pain points one by one , Analyze why it hurts me , And my treatment .

One 、proto File loading problem

The first pain point I encountered was proto File loading problem . Someone might ask , The loading method is very simple :

...
let builder = new protobuf.Builder();
protobuf.loadProtoFile('aaa.proto', builder);
protobuf.loadProtoFile('bbb.proto', builder);
...
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

protobufjs It's an excellent library , He provided loadProtoFile The interface is simple and direct , But will it be like this in real project development ?proto The document was designed from the beginning , Fixed ? Will the file name be changed ? The file will add 、 Delete ?

Pain analysis

I only have my first day in cocos-js Project use proto Time will be one by one proto The file name is written in loadProtoFile In the parameters of , Because it was a project I participated in halfway , I found the problem at that time :

  1. Pathname 、 The file is long and easy to write wrong words .

  2. Agreements will continue to be added during project development , Can write omission , Load less proto file .

  3. Some reasons will change proto file name , The original loading was not modified in time , There will be an error when loading .

  4. Manual handwriting of this loading file will be very tiring , inefficiency , It's easy to make mistakes , In the case of a large number of documents, it consumes brain cells extremely .

terms of settlement

Write code to generate code

My solution is to write a program , scanning proto File directory , Generate an array of file lists , So as to completely liberate manual operation .

//protoFiles.js  Automatically generated files with scripts 
module.exports = [
  res/proto/aaa.proto,
  res/proto/bbb.proto,
  res/proto/zzz.proto,
  res/proto/login/xxx.proto
  ...
]

//pbhelper.js  Write a loader 
let protoFiles = require('protoFiles'); // Import automatically generated proto File list 
...
loadProtoFile() {
    let builder = new protobuf.Builder();
    // Traverse the file name , Load one by one 
    protoFiles.forEach((protoFile) => {
        protobuf.loadProtoFile(protoFile, builder);
    })
    ...
}
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

Never worry again proto File loading problem .

Liberate more manual operations

Writing proto While scanning the script , Can also be proto Synchronize files to your own project directory , To solve the problem proto Manual copy and paste of files , If you want to go further , Can also be svn/git The pull was made for .
Summarize what the script does :

1. from svn or git Get the latest proto file (svn: svn up, git: git pull origin master)
2. take proto Synchronize files to the project directory
3. Scan the project directory for proto file , Generate an array of file lists

Creator New discoveries in

As early as Creator Use in proto I also use the above method , But with the right Creator More and more , I was thinking. ,Creator Didn't you manage all our resources ?cc.loader.loadResDir Not to load all resources in a directory , Can there be a simpler way ? So I tried to debug loadResDir The function was pleasantly surprised .

let files = [];
//xxx yes assets/resources A directory name under the directory 
cc.loader._resources.getUuidArray('xxx', null, files);
//files Will get all the file names 
cc.log(files);
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

Through this discovery , You can eliminate the need to generate protoFiles.js The work of .

Two 、proto Object instantiation problem

proto Object instantiation is a pain point , It is estimated that many people will feel a little fussy .protobufjs Isn't the operation method provided , So simple :

// Instantiate login request 
let loginReq = new pb.LoginRep();
loginReq.account = 'zxh';
loginReq.password = '123456';
// If net It's a encapsulated network module 
net.send(pb.ActionCode.LOGIN, loginRsp, (data) => {
    // Receive the data , Deserialization 
    let loginRsp = pb.LoginRsp.decode(data);
    ...
});
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

If you have done network development, you should understand the above code , Here is a simple explanation :

1.xxxRep Is the client request message ,xxxRsp Is the server response message , Paired design requests 、 The response protocol is easy to manage .
2.pb.ActionCode.LOGIN Is a constant definition , Is the designed request opcode , It is used by the server to identify that the message you send is a login request , Not the others , Otherwise, the serialized binary content server cannot deserialize .
3. There are no clients here proto Object serialization operation , Because it can be encapsulated into net.send Function , So it's not enough to be a pain point .
4.net.send The callback function in is the client response handler , Get the data sent by the server through parameters , Because binary data , So we need to use pb.LoginRsp.decode(data) deserialize .

Pain analysis

let loginReq = new pb.LoginRep();

  1. stay js Use in proto There is a characteristic ,proto The object is generally IDE There is no code hint and coloring , In use call proto The input efficiency of object decoding is low , It's also easy to get the wrong number .

  2. This code exposes the details of the protocol , If pb.LoginRep I don't know if I changed my name , The code will report an error .

  3. net.send(pb.ActionCode.LOGIN, loginReq, () => { }) It's already the login message sent , Why do you need an opcode ? It feels a little cumbersome 、 repeat .

terms of settlement

Factory mode

Would it be more refreshing if it could be like the following :

// Use the factory function to get LoginReq object 
let req = pb.newReq(pb.ActionCode.LOGIN);
req.account = 'zxh';
req.password = '123456';
// Do a little action in the factory function :req.action = pb.ActionCode.LOGIN
//send The message number parameter is not required .
net.send(req, ...);
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

adopt pb.newReq Hide agreement details , You don't need to worry about the name of the message , What do you use protobuf library , Back to req Bind on action Message number reduction calls send Duplicate parameters for , The upper layer operation is simple and clear .
In addition to designing factory functions , You also need to define pb.ActionCode.LOGIN, So that it can be IDE Automatic prompt 、 Code completion , Text coloring , We'll save a lot of worry .

3、 ... and 、proto Object deserialization

Let's look at the deserialization scenario

...
// send data ,net If it is a encapsulated network module 
net.send(pb.ActionCode.LOGIN, loginReq, (data) => {
    // Sending a login request , When deserializing, use login response , Otherwise it will fail 
    let loginRsp = pb.LoginRsp.decode(data);
    ...
});
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

Pain analysis

Deserialization becomes a pain point in part for the same reason as instantiation , And when you receive a response , You should use that proto Object deserialization will kill many brain packets , Especially when the protocol message name is designed without paying attention to the specification, it is easier to make mistakes .

terms of settlement

1. Design communication protocol header
2. request \ Response unique serial number
3. Factory mode


The communication protocol header is the client 、 When the server receives binary data , A fixed protocol structure can be used to reverse the sequence, also known as decoding . After decoding, basic data can be obtained , For example, routing number 、 Time stamp 、 user ID、 Lower layer protocol data ( Binary system ) etc. , About the following :

message PBMessage{
    int32 action = 1;     // The message number is used to indicate data Field ( Identifies the underlying protocol type )
    int32 sequence = 2;   // Request sequence 
    uint64 timestamp = 3; // Time stamp 
    int32 userID = 4;     // Account number 
    bytes data = 5;       // Request or response data ( Serialized binary data )
}
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

Among them sequence Field is when the client sends a request to the server , The only one generated ID. When the server responds to your request , Return this sequence, Through this sequence + action You can determine the object of your response message , So as to correctly decode .

// Received network data 
message(event) {
    var pbMessage = pb.PBMessage.decode(event.data);
    // The parameter object when the request is fetched from the cache object 
    var obj = this.cache[pbMessage.sequence];
    // Delete cached data 
    delete this.cache[pbMessage.sequence];
    try{
        // Check whether the cached data exists 
        if (!obj) {
            return;
        }
        // Create a response object using a factory 
        let rsp = pb.newRsp(obj.action, obj.data);
        // The callback function when calling the request  
        obj.callback(rsp);
    }catch(e) {
      cc.log(' Processing response error ');
    }        
}
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  1. cache It's cache net.send The parameters of the include :action、sequence、callback, among sequence Is automatically generated and takes it as key.

  2. When server data is received , Decode first PBMessage, Use the decoded sequence To find out action.

  3. Use action and data As an argument to the response factory function , Deserialize the response object .

  4. Call the response handler .

At this time, the response function can easily handle the business

// send data ,net If it is a encapsulated network module 
net.send(loginReq, (loginRsp) => {
    // Direct access to the response object , No need to decode 
    this.label.string = loginRsp.player.name;
    ...
});
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

Core issues

Whether solving instantiation or deserialization , The core problem is to implement the two factory functions

let req = newReq(action);
let rsp = newRsp(action, data);

The premise of implementing these two factory functions is to explicitly request the opcode 、 Request object 、 The response object , You need to create a mapping table , Similar to the following definition

//proto In the definition of Action
enum ActionCode {
  LOGIN: 1,
  LOGOUT: 2,    
}

//protoMap.js file 
protoMap = {
    1: {
        req: pb.LoginRes,
        rsp: pb.LoginRsp,
    }
    ...
}
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

With protoMap The factory function is simple

// Factory function 
let protoMap = require('protoMap');
// Request factory function 
newReq(action) {
  let obj = protoMap[action];
  let req = new obj.req();
  req.action = action;
  return req;
}

// Response factory function 
newRsp(action, data) {
  let obj = protoMap[action];
  return obj.rsp.decode(data);
}
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
Four 、protoMap How to come ?

Are all our problems solved ? If you think it's all solved , It's too early to be happy .
at present protoMap.js Documents need to be written manually , The same question comes again .

Pain analysis

1 There are dozens of requests for a project and server , More than hundreds of thousands , Manual maintenance protoMap It's very difficult .
2. Write this by hand protoMap.js The document is added to the agreement 、 modify 、 Error prone when deleting .
3. If something goes wrong, it's hard to find , The problem can only be exposed where it is called .

terms of settlement

Write code to generate code

because protoMap.js It's based on proto The definition of dynamic , My approach is to analyze... Through a program proto File generation protoMap Code . But here to make protoMap The generator should not be too complicated , I am here proto Definition ActionCode I did a little tricks when I was

//proto In the definition of Action
enum ActionCode {
  LOGIN: 1,  //LoginReq;LoginRsp;
  LOGOUT: 2, //LogoutReq;LogoutRsp;   
}
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

In defining ActionCode when , We annotate each message code , The first is to request , The second is the response .
If at the time of designing the agreement , There can be strict specifications, and the notes can be written simply .

enum ActionCode {
  LOGIN: 1,  //Login
  LOGOUT: 2, //Logout
}
     
  • 1.
  • 2.
  • 3.
  • 4.

By means of ActionCode Add some small hands and feet , Then analyze the text , Generate protoMap It would be much simpler . stay protoMap In the generator , You can check the request written in the comment 、 Whether the response object is correct .

Another solution is to add comments on the request protocol :

//action:1
message LoginReq {
    ...
}

//action:2
message LogoutReq {
  ...
}
     
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

I have also used this scheme in the project , It can also be easily extracted and generated protoMap.

5、 ... and 、 The last pain

About protobuf stay js There is still one last pain left in the , That's the current situation IDE Can't support proto Object properties

Automatic completion , Code hinting , Text coloring

let req = pb.newReq(pb.ActionCode.LOGIN);
req.useName = 'zxh'; // This is supposed to be userName Written as useName
req.pwd = '123456';  // This is supposed to be password Written as pwd
     
  • 1.
  • 2.
  • 3.

Pain analysis

1.js There is no code prompt in, which is easy to make clerical mistakes , And most of the problems will not be exposed until the code runs .
2. Without automatic completion, you need to type a lot more words .
3. No function coloring , Knock out the code and feel uneasy .

 

terms of settlement

My current solution to this problem is , take proto Object generation corresponding to js Code , If you want to do better , You can learn Creator like that , Generate a d.ts file .

6、 ... and 、 Be aware of the pain in your heart

The development experience is not perceived in development , It's also hard to be aware of the user experience , Because I am the user of my own project . Can't feel the pain , How to solve the pain ?

 


Welcome to your attention 「 Queerte 」 WeChat official account , Code 、 Have a tutorial 、 There's video 、 There's a story , Let's play together !

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

 

版权声明
本文为[wx5d33905354cb6]所创,转载请带上原文链接,感谢
https://chowdera.com/2021/08/20210808153459067c.html

随机推荐