flatbuffers 在cocos2dx中的应用

这篇文章 接着 前面的http://my.oschina.net/chenleijava/blog/423503,继续说说flatbuffers的应用

这里主要说的是flatbuffer 作为以cocos2dx引擎为基础 ,网络部分的序列化方案采用flatbuffers。

之所以考虑flatbuffers,依赖性低 ,性能较好。

这里只谈论设计方案,该设计方案对代码设计和结构上有一定的约定(相对订制):

客户端 会接受来自服务器的响应,解码到消息,进行数据处理。针对flatbuffers生成的类和对应处理该消息事的方案映射

在以前protbuffer中有提过(http://my.oschina.net/chenleijava/blog/376560)。这里依旧采用此方案:

注意以下所提及的类或者接口都是代码生成器生成(主要适用于这类方案 定制的,采用java编写),会解析fbs文件,生成对应的controller 文件。编写逻辑 只在

virtual void dispatcherMessage(char *data);

中完成;

fbs测试:

//1.使用gen Tools 生成对应客户端代码你需要严格约定
//2.table命名约定 上下行消息 xxRequest xxResponse
//3.前后端交互消息 存在一个消息ID;命名约定: msgID
//4.命名空间 使用gen ---主要限制前端

namespace  gen;

table LoginFlatRequest{

msgID:int=1;
username:string;


}

table LoginFlatResponse{
msgID:int=2;
time:long;
}

table BattleRequest{
 msgID:int=3;

}

table BattleResponse{
 msgID:int=4;

}


接口类设计:IController.h

//warn this is gen file,don't modify it 
#ifndef _ICONTROLLER_H
#define _ICONTROLLER_H
#include <map>
#include "cocos2d.h"
#include "flatgen/Game_generated.h"
using  namespace cocos2d;
using namespace gen;
using  namespace std;
#ifndef delete_array
#define delete_array(p)     do { if(p) { delete[] (p); (p) = nullptr; } } while(0)
#endif //delete_array
class IController {

public:
  IController() { }
 /**
  注册消息ID 和对应处理controller的关系
  以便于快速索引处理
  */
  static std::map<int, IController *> controller_map;
  /**
  负责处理数据分发
  */
  virtual void dispatcherMessage(char *data);
/**
 * 获取对应的消息ID
 */
 virtual int msgID()=0;
  /**
  转化对应消息为flatbuffers实体
  data+4: 消息ID 占用4 字节
  */
  template <typename  T> const  T* getRoot(char* data);
 /**
  * @Author 石头哥哥, 15-06-01 23:06:26
  *
  * @brief  注册controoler and mapper
  */
  static void registerMapperController();
};
template<typename T>
inline const T *IController::getRoot(char *data) {
    return flatbuffers::GetRoot<T>(data+4);
}
#endif//_ICONTROLLER_H

.cpp

//this is gen file,don't modify it 
#include "IController.h"
#include "LoginFlatController.h"
#include "BattleController.h"

map<int, IController *> IController::controller_map;
void IController::dispatcherMessage(char *data) {}
/**
 * 注册controller mapper
 */
void IController::registerMapperController() {
  log("%s", "registerMapperController开始注册controller... ...");
  controller_map[LoginFlatController::controller->msgID()]=LoginFlatController::controller;
  controller_map[BattleController::controller->msgID()]=BattleController::controller;
}


生成对应的子类:LoginFlatController.h

//this is gen file,don't modify it 
#ifndef LoginFlatController_h
#define LoginFlatController_h
#include "IController.h"
class LoginFlatController:public IController{
public:
static LoginFlatController *controller;
public:
/**
负责处理数据分发    处理来自服务器数据
*/
virtual void dispatcherMessage(char *data);
/**
 * 获取对应的消息ID
 */
virtual int msgID() override;
};
#endif //LoginFlatController_h

.cpp

//this is gen file,logic controler 

#include "LoginFlatController.h"
LoginFlatController*LoginFlatController::controller=new LoginFlatController;

int LoginFlatController::msgID(){
 flatbuffers::FlatBufferBuilder builder;
 builder.Finish(CreateLoginFlatResponse(builder));
 auto msgID = flatbuffers::GetRoot<LoginFlatResponse>(builder.GetBufferPointer())->msgID();
 builder.ReleaseBufferPointer();
 return msgID;
}

/**
负责处理数据分发    处理来自服务器数据
 */
void LoginFlatController::dispatcherMessage(char *data) {

   auto loginflatresponse=getRoot<LoginFlatResponse>(data);
  //TODO::处理来自服务器数据LoginFlatResponse

}

因为生成代码的工具还在开发中。目前只完成了cpp生成 ,后期支持java(服务器端)---暂时只定制flatbuffer代码生成。

后期开源分享,这里提供java代码生成类:

package com.genflat.mapper;/*
 * Copyright (c) 2015.
 * 游戏服务器核心代码编写人石头哥哥拥有使用权
 * 最终使用解释权归创心科技所有
 * 联系方式:E-mail:[email protected] ;
 * 个人博客主页:http://my.oschina.net/chenleijava
 * powered by 石头哥哥
 */

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;

/**
 * @author 石头哥哥
 *         </P>
 *         Date:   2015/6/2
 *         </P>
 *         Time:   9:35
 *         </P>
 *         Package: dcServer-parent
 *         </P>
 *         <p/>
 *         注解:
 */
public class GenFlatCpp {

    /**
     * 客户端处理消息类型 注意消息命名XXXResponse--- 服务器响应消息类型
     */
    private static final String client = "Response";

    /**
     * 用于生成处理消息类型引用的变量名  注意是静态类型
     */
    private static final String controllorname = "controller";


    /**
     * 对应客户端处理来自服务器端消息命名
     * xxxResponse
     * <p/>
     * table LoginResponse{
     * objectID:int=2;
     * username:string;
     * }
     * <p/>
     * 生成处理来自服务器端的  Response 类型消息    <p/>
     * 自动生成映射的mapper  如:controller_map[msgID<LoginResponse>()]=LoginResponseController::controller; <p/>
     * 同时生成对应的controller处理部分  <p/>
     * 逻辑编写部位:virtual void dispatcherMessage(char *data);   <p/>
     *
     * @param fbspath        flatbuffer fbs 所在文件路径        <p/>
     * @param controllerpath 对应controller生成路径  <p/>
     * @throws IOException
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static void genCppMapper(String fbspath, String controllerpath) throws Exception {
        File file;
        //get fbs table list
        file = new File(fbspath);
        String[] fbsNames = file.list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".fbs");
            }
        });
        if (fbsNames == null) {
            throw new Exception("fbs not found in res!!!");
        }

        ArrayList<String> fbsPaths = new ArrayList<String>();

        for (String fbsname : fbsNames) {
            fbsPaths.add(fbspath + "/" + fbsname);
        }

        //读取所有fbs文件 获取文件中的table name
        ArrayList<String> mapperTable = new ArrayList<String>();

        //loading fbs and filter table name
        for (String fbs : fbsPaths) {
            file = new File(fbs);
            RandomAccessFile randomAccessFile;
            try {
                randomAccessFile = new RandomAccessFile(file, "r");     //O_RDONLY
                String txt = "";
                while (txt != null) {
                    txt = randomAccessFile.readLine();
                    if (txt != null
                            && txt.startsWith("table")) {
                        String temp = txt.substring(0, txt.lastIndexOf("{"))
                                .replace("table", "");
                        if (temp.endsWith(client)) mapperTable.add(temp.trim());

                    }
                }
                randomAccessFile.close();  //close   randomAccessFile
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        fbsPaths.clear();


        ArrayList<String> controllerList = new ArrayList<String>();


        //for icontroller
        StringBuilder mapperBuilder = new StringBuilder();
        //  controller_map[LoginFlatController::controller->msgID()]=LoginFlatController::controller;
        for (String table : mapperTable) {
            String controllername = table + "Controller";
            controllername = controllername.replace(client, "");
            mapperBuilder.append("  controller_map[")
                    .append(controllername)
                    .append("::")
                    .append(controllorname+"->msgID()")
                    .append("]")
                    .append("=")
                    .append(controllername)
                    .append("::")
                    .append(controllorname)
                    .append(";\n");
            controllerList.add(controllername);//add controller
        }

        mapperTable.clear();


        ArrayList<String> genIcontrollerHeader = new ArrayList<String>();
        genIcontrollerHeader.addAll(controllerList);
        /**
         * don't override  had gen xxxcontroller
         * because  maybe had logic in it!!!!
         */
        String[] exsiteController = new File(controllerpath).list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".cpp") && !name.endsWith("IController.cpp");
            }
        });

        for (String name : exsiteController) {
            String temp = name.substring(0, name.indexOf("."));
            if (controllerList.contains(temp)) {
                controllerList.remove(temp);
            }
        }



        StringBuilder builderCpp = new StringBuilder();
        //gen .h file
        for (String controller : controllerList) {
            String define = controller + "_h";
            builderCpp.append("//this is gen file,don't modify it \n")
                    .append("#ifndef")
                    .append(" ").append(define).append("\n")
                    .append("#define")
                    .append(" ").append(define).append("\n")
                    .append("#include \"IController.h\"")
                    .append("\n")
                    .append("class ")
                    .append(controller)
                    .append(":").append("public IController{")
                    .append("\n")
                    .append("public:")
                    .append("\n")
                    .append("static ")
                    .append(controller)
                    .append(" *")
                    .append(controllorname)
                    .append(";\n")
                    .append("public:\n")
                    .append("/**\n" +
                            "负责处理数据分发    处理来自服务器数据\n" +
                            "*/\n")
                    .append("virtual void dispatcherMessage(char *data);\n")
                    .append("/**\n" +
                            " * 获取对应的消息ID\n" +
                            " */\n")
                    .append("virtual int msgID() override;\n")
                    .append("};\n")
                    .append("#endif")
                    .append(" //").append(define);
            byte[] cpp_h = builderCpp.toString().getBytes();
            builderCpp.delete(0, cpp_h.length);

            //write header
            File controllerFile = new File(controllerpath);
            if (!controllerFile.exists()) {
                controllerFile.mkdirs();
            }
            FileUtils.writeByteArrayToFile(new File(controllerpath + "/" + controller + ".h"), cpp_h);




            //gen controller cpp file
            String tableName = controller.substring(0, controller.indexOf("Controller")) + client;
            String lowTablename = tableName.toLowerCase();
            builderCpp.append("//this is gen file,logic controler \n")
                    .append("\n")
                    .append("#include")
                    .append(" ")
                    .append("\"")
                    .append(controller).append(".h")
                    .append("\"")
                    .append("\n")
                    .append(controller).append("")
                    .append("*")
                    .append(controller)
                    .append("::")
                    .append(controllorname).append("=")
                    .append("new ").append(controller)
                    .append(";\n")
                    .append("\n")
                    .append("int ")
                    .append(controller+"::msgID(){\n")
                    .append(" flatbuffers::FlatBufferBuilder builder;\n" +
                            " builder.Finish(Create"+tableName+"(builder));\n" +
                            " auto msgID = flatbuffers::GetRoot<"+tableName+">(builder.GetBufferPointer())->msgID();\n" +
                            " builder.ReleaseBufferPointer();\n" +
                            " return msgID;\n")
                    .append("}\n\n")
                    .append("/**\n" +
                            "负责处理数据分发    处理来自服务器数据\n" +
                            " */\n")
                    .append("void ").append(controller).append("::")
                    .append("dispatcherMessage(char *data) {\n\n").append("   auto ")
                    .append(lowTablename)
                    .append("=getRoot<")
                    .append(tableName)
                    .append(">(data);\n")
                    .append("  //TODO::处理来自服务器数据")
                    .append(tableName)
                    .append("\n\n")
                    .append("}");

            byte[] cpp = builderCpp.toString().getBytes();
            builderCpp.delete(0, cpp.length);
            FileUtils.writeByteArrayToFile(new File(controllerpath + "/" + controller + ".cpp"), cpp);
        }









        //IController always override
        // gen IController  generated header name
        ArrayList<String> generatedHeaders = new ArrayList<String>();
        String tempname;
        String tempnameup;
        char first;
        for (String name : fbsNames) {
            tempname = name.substring(1, name.lastIndexOf("."));
            tempnameup = name.substring(0, name.lastIndexOf(".")).toUpperCase();
            first = tempnameup.charAt(0);
            generatedHeaders.add(first + tempname);
        }


        builderCpp.append("//warn this is gen file,don't modify it \n")
                .append("#ifndef ").append("_ICONTROLLER_H\n")
                .append("#define ").append("_ICONTROLLER_H\n")
                .append("#include <map>\n")
                .append("#include \"cocos2d.h\"\n");
        //append generated header include
        for (String name : generatedHeaders) {
            builderCpp.append("#include \"flatgen/").append(name)
                    .append("_generated.h\"").append("\n");
        }
        generatedHeaders.clear();

        builderCpp.append("using  namespace cocos2d;\n")
                .append("using namespace gen;\n")
                .append("using  namespace std;\n")
                .append("#ifndef delete_array\n")
                .append("#define delete_array(p)     do { if(p) { delete[] (p); (p) = nullptr; } } while(0)\n")
                .append("#endif //delete_array\n")
                .append("class IController {\n")
                .append("\n")
                .append("public:\n")
                .append("  IController() { }\n")
                .append(" /**\n" +
                        "  注册消息ID 和对应处理controller的关系\n" +
                        "  以便于快速索引处理\n" +
                        "  */\n")
                .append("  static std::map<int, IController *> controller_map;\n")
                .append("  /**\n" +
                        "  负责处理数据分发\n" +
                        "  */\n")
                .append("  virtual void dispatcherMessage(char *data);\n")
                .append("/**\n" +
                        " * 获取对应的消息ID\n" +
                        " */\n")
                .append(" virtual int msgID()=0;\n")
//                .append(" /**\n" +
//                        " * @Author 石头哥哥, 15-06-01 23:06:00\n" +
//                        " *\n" +
//                        " * @brief  获取生成消息对应的msgID\n" +
//                        " *\n" +
//                        " * @return <#return value description#>\n" +
//                        " */\n")
//                .append("  template<typename T> static int msgID();\n")
                .append("  /**\n" +
                        "  转化对应消息为flatbuffers实体\n" +
                        "  data+4: 消息ID 占用4 字节\n" +
                        "  */\n")
                .append("  template <typename  T> const  T* getRoot(char* data);\n")
                .append(" /**\n" +
                        "  * @Author 石头哥哥, 15-06-01 23:06:26\n" +
                        "  *\n" +
                        "  * @brief  注册controoler and mapper\n" +
                        "  */\n")
                .append("  static void registerMapperController();\n")
                .append("};\n")
//                .append(" template<typename T>\n")
//                .append("inline  int IController::msgID() {\n" +
//                        "   flatbuffers::FlatBufferBuilder builder;\n" +
//                        "   builder.Finish(CreateLoginRequest(builder));\n" +
//                        "   auto msgID = flatbuffers::GetRoot<T>(builder.GetBufferPointer())->msgID();\n" +
//                        "   builder.ReleaseBufferPointer();\n" +
//                        "   return msgID;\n" +
//                        "}\n")
                .append("template<typename T>\n" +
                        "inline const T *IController::getRoot(char *data) {\n" +
                        "    return flatbuffers::GetRoot<T>(data+4);\n" +
                        "}")
                .append("\n").append("#endif//_ICONTROLLER_H");

        byte[] data = builderCpp.toString().getBytes();
        builderCpp.delete(0, data.length);
        FileUtils.writeByteArrayToFile(new File(controllerpath + "/IController.h"), data);


        //gen icontroller cpp
        builderCpp.append("//this is gen file,don't modify it \n")
                .append("#include \"IController.h\"\n");

        for (String controller : genIcontrollerHeader) {
            builderCpp.append("#include ")
                    .append("\"").append(controller).append(".h")
                    .append("\"\n");
        }
        genIcontrollerHeader.clear();
        controllerList.clear();
        builderCpp.append("\n")
                .append("map<int, IController *> IController::controller_map;\n")
                .append("void IController::dispatcherMessage(char *data) {}\n")
                .append("/**\n" + " * 注册controller mapper\n" + " */\n" + "void IController::registerMapperController() {\n" +
                        "  log(\"%s\", \"registerMapperController开始注册controller... ...\");\n")
                .append(mapperBuilder.toString()).append("}\n");
        data = builderCpp.toString().getBytes();
        builderCpp.delete(0, data.length);
        FileUtils.writeByteArrayToFile(new File(controllerpath + "/IController.cpp"), data);

        System.out.println("-------gen success!!!---------------------");

    }


}
package com.genflat.mapper;/*
 * Copyright (c) 2015.
 * 游戏服务器核心代码编写人石头哥哥拥有使用权
 * 最终使用解释权归创心科技所有
 * 联系方式:E-mail:[email protected] ;
 * 个人博客主页:http://my.oschina.net/chenleijava
 * powered by 石头哥哥
 */

import java.io.File;
import java.io.IOException;

/**
 * @author 石头哥哥
 *         </P>
 *         Date:   2015/6/2
 *         </P>
 *         Time:   9:34
 *         </P>
 *         Package: dcServer-parent
 *         </P>
 *         <p/>
 *         注解:
 */
public class GenFlatMapper {


    /**
     * default gen c++
     * args[0] -l
     * args[1]  (java ,cpp)
     * args[2] -p
     * args[3]  (controller file path)
     * args[4]-o
     * args[5] fbs file path
     *
     * @param args
     */
    public static void main(String[] args) throws IOException {
        String show = ">>>>flatbuffer gen " +
                "\n该工具可以生成基于flatbuffers的代码,接口说明如下\n" +
                "使用命令:java -jar genflatMapper.jar -l cpp -c controllerpath -f fbspath \n" +
                "-l :编程语言类型 这里暂时支持 c++;\n" +
                "-p:  游戏中处理对应消息的controller文件路径;\n" +
                "-o: 编写的fbs文件路径;\n" +
                "游戏中fbs针对上行下行table命名规则有严格约定,如不使用该约定\n" +
                "使用该工具无效,详见readme! ";
        System.out.println(show);

        if (args.length != 0) {
            System.out.println("--------begin gen client code ----------------");
            if (args.length < 6) {
                System.err.println(show);
                System.exit(0);
            }
            String l = args[1];
            String controllerpath = args[3];
            String fbspath = args[5];
            if (l.equals("java")) {
                System.err.println("only support client , c++ \n");
                System.err.println("use eg:\n" +
                        "java -jar genflatMapper.jar -l cpp " +
                        "-c controllerpath -f fbspath ");
                System.exit(0);
            }else if (l.equals("cpp")) {
                try {
                    GenFlatCpp.genCppMapper(fbspath, controllerpath);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        } else {
            try {
                //gen current file
                System.out.println("tools will gen to current files,if your fbs in res file!!!");
                File file = new File("./controller");
                if (!file.exists()) file.mkdirs();
                //find fbs
                GenFlatCpp.genCppMapper("res", "./controller");
            } catch (Exception e) {
                e.printStackTrace();
            }

        }


    }

}


该生代码成器约定如下--如果你使用该方案,那么请严格遵循改约束,或许你有更好的idea:

###基于flatbuffers ,使用fbs文件生成对应前端代码。
###完成对应的controller和消息ID映射,目前暂时支持c++。


>1.使用gen Tools 生成对应客户端代码你需要严格约定
2.table命名约定 上下行消息 xxRequest xxResponse
3.前后端交互消息 存在一个消息ID;命名约定: msgID
>4.命名空间 使用gen ---主要限制前端 
 
namespace gen;
//下行消息 xxRequest
table LoginRequest{
     msgID:int=1; //消息ID msgID
     username:string;
}
//下行消息 xxResponse
table LoginResponse{
     msgID:int=1;     //消息ID msgID
     username:string;
}

####如何使用:
该工具可以生成基于flatbuffers的代码,接口说明如下
使用命令:
java -jar genflatMapper.jar -l cpp -p controllerpath -o fbspath 
>-l :编程语言类型 这里暂时支持 c++;
>-c:  游戏中处理对应消息的controller文件路径;
>-f: 编写的fbs文件路径;

生成对应的文件如下:

flatbuffers 在cocos2dx中的应用

上面代码都是生成的。

因为无法上传jar,如果你需要 留言你的邮箱 !

你可能感兴趣的:(FlatBuffers,cocos2d-x)