孙广东 2015.7.4
http://www.open-open.com/lib/view/open1441004786315.html
虽然FB的反序列化超级快,但是数据大小还是蛮大的(可能的原因是不需要解析,直接访问binary buffer,所以不能对数据进行压缩),带来的传输或存储开销,可能会抵过一部分反序列化的时间。感觉上,FB的使用场景上还是很受限,在移动端可能是一个应用场景。
protocol buffer的作者后来又弄了一个Cap'n Proto ( 但是好像应用场景没有太火 )
http://google.github.io/flatbuffers/flatbuffers_support.html
提供了对 Unity游戏引擎C#的 支持!(本身 FlatBuffers 就是 移动平台移动开发 而出的!)
关于Unity的示例代码 必须要学习啊!!!!!!
http://exiin.com/blog/flatbuffers-for-unity-sample-code/
FlatBuffers 是 Google为游戏和移动应用而开发的开源、跨平台的序列化库。支持 C++, Java, C#, Go, Python and JavaScript等语言。中国有5亿智能手机,其中低端设备占多数,在 CPU 和内存都受限的情况下,能否开发出高性能且低内存占用的 Android程序决定了你的应用的用户覆盖率和留存率。
在Unity使用 FlatBuffers 作为存储格式(移动版)
翻译: http://qiita.com/akerusoft/items/8c10f8a40fee722e6d1b
Unity提供了 PlayerPrefs作为本地的数据存储, 但是速度不是很快!
下面就是一个测试使用 FlatBuffers
Unity 5.3.3f1 版本
FlatBuffers 1.3.0版本
运行在 Android /IOS 测试
FlatBuffers序列化保存为二进制。
cocos2dx 游戏数据使用
还应用在Facebook上。
FlatBuffers,分为 结构定义 (架构) 和 数据
它要在服务器和客户端可用。
使用 FlatBuffers 的流程。
schema language (aka IDL, Interface DefinitionLanguage)
namespace Data;
file_identifier"MYFI";
unionData
{
MonsterDataV1
}
tableRoot
{
data:Data;
}
tableMonsterDataV1
{
name:string;
hp:uint;
hitRate:float;
speed:uint;
luck:uint;
}
tableMonsterDataV2
{
name:string;
hp:uint;
hitRate:float;
speed:uint;
luck:uint;
defence:uint;
}
root_typeRoot;
来自 <http://qiita.com/akerusoft/items/8c10f8a40fee722e6d1b>
首先是表table MonsterDataV1
这假设是在 rpg 游戏中使用的怪物结构。
表table 字段声明(类型写在 后面)
字段是通过使用 内置类型 进行描述。
除了内置类型,你也可以使用自定义的类型(除了当前table)。
以下是union的数据。
这是种枚举类型,主要特征 以table表作为字段。
在本例中是定义的字段 只有 MonsterDataV1
union 中的所有字段占用同一个内存存储(同一时间也只有一个字段有效)。
如果你也可以添加 MonsterDataV2 字段,但数据也只有一个存储。(听起来像一个 c 语言中的union)
基于这种分析。
"file_identifier"是写在 文件的开头的 文件 ID,您可以使用 4 ASCII 字符 (字节 4-7)。
它是可能要检查服务器发送数据,等等......
升级注意事项
应用程序或游戏,要进行数据升级,当一项新功能被增加到应用程序
但是 schema 有一定的局限性。
升级的Demo
小的是升级,将向表中添加字段。
之前的MonsterDataV1声明:
table MonsterDataV1
{
name:string;
hp:uint;
hitRate:float;
speed:uint;
luck:uint;
}
添加新的字段:
table MonsterDataV1
{
name:string;
hp:uint;
hitRate:float;
speed:uint;
luck:uint; (deprecated)
hoge1:[ubyte];
hoge2:bool;
hoge3:int;
hoge4:long;
hoge5:long; (deprecated)
hoge6:long;
hoge7:long;
hoge8:long; (deprecated)
hoge9:long;
}
union中添加新的 table类型!!!!!
unionData
{
MonsterDataV1,
MonsterDataV2
}
程序的输出
Flatc 可执行文件程序的输出。
Windows 版本是 exe 在github页上的窗体。
对于一个 c# 文件生成用下面的命令。
flatc.exe -o OUTPUT_PATH -n--gen-onefile inputfile.fbs
"OUTPUT_PATH" 请设置路径输出。
要输出一个 c# 文件参数 '-n'
"--gen-onefile" 生成 cs 文件时将被输出到一个单独的文件。
您声明数据定义schema文件的名称是 "inputfile.fbs"(所以schema对文件名和后缀名美没有任何限制吧!) 。
或者直至这样写: flatc -n schemaTemplate.txt --gen-onefile
要导入到Unity的运行时文件
运行 FlatBuffers 项目将生成的Dll 导入到Unity的尝试
从官方github DL 。
http://blog.csdn.net/u010019717
在脚本文件中的序列化/反序列化
要反序列化demo github.
在 Test1 test2 序列化反序列化过程中的执行。
ButtonCreate.cs
stringpath = Path.Combine(Application.persistentDataPath, DataName.FILE_NAME);
if(File.Exists(path))
File.Delete(path);
FlatBufferBuilderbuilder = new FlatBufferBuilder(1);
intoffestData;
Data.Data dataType;
Offset<Data.MonsterDataV1>data = Data.MonsterDataV1.CreateMonsterDataV1(builder,builder.CreateString(name), hp, hitRate, speed, luck);
offestData = data.Value;
dataType = Data.Data.MonsterDataV1;
Data.Root.StartRoot(builder);
Data.Root.AddDataType(builder,dataType);
Data.Root.AddData(builder,offestData);
Offset<Data.Root> endOffset =Data.Root.EndRoot(builder);
Data.Root.FinishRootBuffer(builder,endOffset);
bytes =builder.SizedByteArray();
File.WriteAllBytes(path,bytes);
反序列化过程:
ButtonLoad.cs
stringpath = Path.Combine(Application.persistentDataPath, DataName.FILE_NAME);
ByteBufferbuffer = new ByteBuffer(File.ReadAllBytes(path));
Data.Rootroot = Data.Root.GetRootAsRoot(buffer);
Data.DatadataType = root.DataType;
switch(dataType)
{
caseData.Data.MonsterDataV1:
Data.MonsterDataV1 monsterV1= root.GetData<Data.MonsterDataV1>(new Data.MonsterDataV1());
if(monsterV1 == null)
{
Debug.LogError("Failed load monster data version1.");
return;
}
textVersion.text= "Version1";
if(Encoding.Default != Encoding.UTF8)
textName.text =Encoding.Default.GetString(Encoding.Convert(Encoding.UTF8, Encoding.Default,Encoding.UTF8.GetBytes(monsterV1.Name)));
else
textName.text =monsterV1.Name;
textHp.text =monsterV1.Hp.ToString();
textHitRate.text =monsterV1.HitRate.ToString();
textSpeed.text =monsterV1.Speed.ToString();
textLuck.text =monsterV1.Luck.ToString();
textDefence.text = "Nodata";
break;
}
每种类型的反序列化过程中的过滤器在union中定义的类型。
这意味着的消息传递时你重置数据定义,应该用来读取数据。
测试代码在 github 上的有一些不同。
github 上的代码有 执行加密过程。
(加密 / 解密处理,处理时间会消耗更长的时间......)
FlatBuffers 的思想
cocos2dx spine
json 输出数据是spine的加载很慢。
以二进制格式是相当棒的。
尝试使用及时研究的利与弊??
Unity中使用 FlatBuffers的案例
翻译自: http://exiin.com/blog/flatbuffers-for-unity-sample-code/
1-第一步下载编译器"flatc"和"FlatBuffers.dll"
Flatc.exe 用于将Schema转换成 c#
您可以下载最新的版本 FlatBuffers.dll︰
https://github.com/google/flatbuffers/releases
首先你要下载最新的 flatc.exe 文件,
然后你进入从Github上下载的源代码:路径下的 "\net\FlatBuffers" 文件夹 并打开"FlatBuffers.csproj"
并编译出 "FlatBuffers.dll",你需要放到Unity的项目中(assets/plugins文件夹内放)。
下一步是创建一个单独的文件夹, 放着我的编译器和 schema 文件︰
,这里我做了一个批处理脚本 ( compile.bat ) 包含这些行︰
flatc -n SaveSchema.txt--gen-onefile @pause
1 2 |
flatc -n SaveSchema.txt --gen-onefile @pause |
用于演示,我将使用一个 SaveSchema.txt 文件︰
//example save file
namespaceCompanyNamespaceWhatever;
enumColor : byte { Red = 1, Green, Blue }
unionWeaponClassesOrWhatever { Sword, Gun }
structVec3 {
x:float;
y:float;
z:float;
}
tableGameDataWhatever {
pos:Vec3;
mana:short = 150;
hp:short = 100;
name:string;
inventory:[ubyte];
color:Color = Blue;
weapon:WeaponClassesOrWhatever;
}
tableSword {
damage:int = 10;
distance:short = 5;
}
tableGun {
damage:int = 500;
reloadspeed:short = 2;
}
root_typeGameDataWhatever;
file_identifier"WHAT";
schema 文件定义了要保存的数据的结构体。
关于schema 语法的更多信息请阅读这个官方文档页面.
一旦你执行compile.bat,它会创建一个名为"SavedSchema.cs"的新文件
Flatc现在生成的几个类的名称,如"WeaponClassesOrWhatever"
这个C#文件是你整个系统的加载和保存schema的作用。
2-下一步,我们如何保存我们的数据?
生成的.cs文件包含的所有类和需保存和加载的数据。
到您的项目生成文件的地方
(也请不要忘记将"FlatBuffers.dll"放到您的项目,否则你会看到一些错误)
然后将此代码︰
// Create flatbufferclass
FlatBufferBuilderfbb = new FlatBufferBuilder(1);
// Create our swordfor GameDataWhatever
//------------------------------------------------------
WeaponClassesOrWhateverweaponType = WeaponClassesOrWhatever.Sword;
Sword.StartSword(fbb);
Sword.AddDamage(fbb,123);
Sword.AddDistance(fbb,999);
Offset<Sword>offsetWeapon = Sword.EndSword(fbb);
/*
// For gun uncommentthis one and remove the sword one
WeaponClassesOrWhateverweaponType = WeaponClassesOrWhatever.Gun;
Gun.StartGun(fbb);
Gun.AddDamage(fbb,123);
Gun.AddReloadspeed(fbb,999);
Offset<Gun>offsetWeapon = Gun.EndGun(fbb);
*/
//------------------------------------------------------
// Create stringsfor GameDataWhatever
//------------------------------------------------------
StringOffset cname =fbb.CreateString("Test String ! time : " + DateTime.Now);
//------------------------------------------------------
// CreateGameDataWhatever object we will store string and weapon in
//------------------------------------------------------
GameDataWhatever.StartGameDataWhatever(fbb);
GameDataWhatever.AddName(fbb,cname);
GameDataWhatever.AddPos(fbb,Vec3.CreateVec3(fbb, 1, 2, 1)); // structs can be inserted directly, no need tobe defined earlier
GameDataWhatever.AddColor(fbb,CompanyNamespaceWhatever.Color.Red);
//Store weapon
GameDataWhatever.AddWeaponType(fbb,weaponType);
GameDataWhatever.AddWeapon(fbb,offsetWeapon.Value);
var offset =GameDataWhatever.EndGameDataWhatever(fbb);
//------------------------------------------------------
GameDataWhatever.FinishGameDataWhateverBuffer(fbb,offset);
// Save the datainto "SAVE_FILENAME.whatever" file, name doesn't matter obviously
using (var ms = newMemoryStream(fbb.DataBuffer.Data, fbb.DataBuffer.Position, fbb.Offset)) {
File.WriteAllBytes("SAVE_FILENAME.whatever", ms.ToArray());
Debug.Log("SAVED !");
}
你写你的数据的方式是 顺序依赖.
你总是要由内而外创建项目.
从一切开始该对象包含(如字符串、 数组、 其他对象) 的对象本身。
基本上,如果使用了其他的对象,就要预先设置其他的对象!
所以这里发生的是︰
我们要先创建的weapon 和string ,因为GameDataWhatever里面使用了他们。
储蓄的一部分可以真的很棘手,我劝你读这篇文章有更好的理解如何存储数据。
3-最后,读取文件是小菜一碟。
可以按任何顺序,如果你想要你甚至不需要去通过的所有值,因为flatbuffers 工作像变魔术一样 !
ByteBuffer bb = newByteBuffer(File.ReadAllBytes("SAVE_FILENAME.whatever"));
if(!GameDataWhatever.GameDataWhateverBufferHasIdentifier(bb)) {
throw new Exception("Identifier testfailed, you sure the identifier is identical to the generated schema'sone?");
}
GameDataWhateverdata = GameDataWhatever.GetRootAsGameDataWhatever(bb);
Debug.Log("LOADEDDATA : ");
Debug.Log("NAME: " + data.Name);
Debug.Log("POS: " + data.Pos.X + ", " + data.Pos.Y + ", " +data.Pos.Z);
Debug.Log("COLOR: " + data.Color);
Debug.Log("WEAPONTYPE : " + data.WeaponType);
switch(data.WeaponType) {
case WeaponClassesOrWhatever.Sword:
Sword sword = new Sword();
data.GetWeapon<Sword>(sword);
Debug.Log("SWORD DAMAGE : " + sword.Damage);
break;
case WeaponClassesOrWhatever.Gun:
Gun gun = new Gun();
data.GetWeapon<Gun>(gun);
Debug.Log("GUN RELOAD SPEED : " + gun.Reloadspeed);
break;
default:
break;
}
我们测试了flatbuffers 对所有主要的移动平台 (iOS,Android,亚马逊的操作系统,Windows Phone), 它工作得很好。
4 Unity中的源代码
如果你想 示例代码和 已编译的flatbuffer 文件 flatc.exe 和 flatbuffers.dll,然后下载此文件︰
FlatBuffersTest.zip
来自 <http://exiin.com/blog/flatbuffers-for-unity-sample-code/>
快速上手
接下来将会简要介绍如何使用FlatBuffers。
开发资源
网络资源
测试案例
http://blog.csdn.net/u010019717
在Google的Benchmark中,已经明确表明了其性能优势,杠杠的啊!
考虑到其扩语言与轻量级的特性,笔者专门自己做了一些较为贴近生产场景的测试,对比的是开发常用的1.1.41版本的fastjson
贴上代码
FlatBuffer与fastjson的比较
packageflatbuffers.test;
importjava.nio.ByteBuffer;
importjava.util.Arrays;
importcom.alibaba.fastjson.JSONObject;
importflatbuffers.FlatBufferBuilder;
importflatbuffers.schema.Product;
public classBufferCompareTest {
static final long LOOP_COUNT = 5000000;
public static void main(String[] args) {
System.out.println("执行"+LOOP_COUNT+"次解析耗时比较");
//FlatBuffers
byte[] dataByte =buildFlatBuffersByte();
long startFlatBuffers =System.currentTimeMillis();
Product p = null;
for (int i = 0; i < LOOP_COUNT; i++){
p =Product.getRootAsProduct(ByteBuffer.wrap(dataByte));
}
System.out.println("FlatBuffers :" + (System.currentTimeMillis() - startFlatBuffers)+"ms");
//JSON
String jsonStr ="{\"marketable\":\"true\",\"costPrice\":15000,\"imgUrl\":\"http://img2.bbgstatic.com/14e2a07cbd5_2_8f02bdb34427ec107124fd1576287310_800x800.jpeg\",\"productBn\":\"MG120394938912345\",\"productId\":123456,\"productName\":\"德国爱他美Aptamil Pre段 0-3个月 日期至16年7-8月左右\",\"salePrice\":15500,\"shopId\":123,\"shopName\":\"云猴全球购\"}";
long startJSON =System.currentTimeMillis();
JSONObject obj = null;
for (int i = 0; i < LOOP_COUNT; i++){
obj =JSONObject.parseObject(jsonStr);
}
System.out.println("JSON : "+ (System.currentTimeMillis() - startJSON)+"ms");
}
private static byte[]buildFlatBuffersByte() {
FlatBufferBuilder fbb = newFlatBufferBuilder(1);
int productBnOS =fbb.createString("MG120394938912345");
int productNameOS =fbb.createString("德国爱他美Aptamil Pre段 0-3个月 日期至16年7-8月左右");
int shopNameOS =fbb.createString("云猴全球购");
int imgUrlOS =fbb.createString("http://img2.bbgstatic.com/14e2a07cbd5_2_8f02bdb34427ec107124fd1576287310_800x800.jpeg");
//注意,创建字符串(createString)要在XXX.startXXX方法执行之前,否则会出现异常
Product.startProduct(fbb);
Product.addProductId(fbb, 123456l);
Product.addShopId(fbb, 123);
Product.addProductBn(fbb, productBnOS);
Product.addProductName(fbb,productNameOS);
Product.addShopName(fbb, shopNameOS);
Product.addImgUrl(fbb, imgUrlOS);
Product.addCostPrice(fbb, 15000l);
Product.addSalePrice(fbb, 15500l);
Product.addMarketable(fbb, true);
int endOffset =Product.endProduct(fbb);
Product.finishProductBuffer(fbb,endOffset);
byte[] originalData =fbb.dataBuffer().array();
byte[] dataByte =Arrays.copyOfRange(originalData, fbb.dataBuffer().position(),(fbb.dataBuffer().position() +fbb.offset()));
return dataByte;
}
}
结果是,还是有点差距的:
执行5000000次解析耗时比较
FlatBuffers : 98ms
JSON : 10375ms
http://blog.csdn.net/u010019717