Code Monkey home page Code Monkey logo

zeroformatter's Introduction

ZeroFormatter

Fastest C# Serializer and Infinitly Fast Deserializer for .NET, .NET Core and Unity.

image

Why use ZeroFormatter?

  • Fastest C# Serializer, code is extremely tuned by both sides of implementation and binary layout(see: performance)
  • Deserialize/Reserialize is infinitly fast because formatter can access to serialized data without parsing/packing(see: architecture)
  • Strongly Typed and C# Code as schema, no needs to other IDL like .proto, .fbs...
  • Smart API, only to use Serialize<T> and Deserialize<T>
  • Native support of Dictionary, MultiDictionary(ILookup)
  • First-class support to Unity(IL2CPP), it's faster than native JsonUtility

ZeroFormatter is similar as FlatBuffers but ZeroFormatter has clean API(FlatBuffers API is too ugly, see: sample; we can not use reguraly) and C# specialized. If you need to performance such as Game, Distributed Computing, Microservices etc..., ZeroFormatter will help you.

Install

for .NET, .NET Core

for Unity(Interfaces can reference both .NET 3.5 and Unity for share types)

Visual Studio Analyzer

Quick Start

Define class and mark as [ZeroFormattable].

Sample

[ZeroFormattable]
public class MyClass
{
    [Index(0)]
    public virtual int Age { get; set; }

    [Index(1)]
    public virtual string FirstName { get; set; }

    [Index(2)]
    public virtual string LastName { get; set; }

    [IgnoreFormat]
    public string FullName { get { return FirstName + LastName; } }

    [Index(3)]
    public virtual IList<int> List { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var mc = new MyClass
        {
            Age = 99,
            FirstName = "hoge",
            LastName = "huga",
            List = new List<int> { 1, 10, 100 }
        };

        var bytes = ZeroFormatterSerializer.Serialize(mc);
        var mc2 = ZeroFormatterSerializer.Deserialize<MyClass>(bytes);

        // ZeroFormatter.DynamicObjectSegments.MyClass
        Console.WriteLine(mc2.GetType().FullName);
    }
}

Serializable target must mark ZeroFormattableAttribute, there public property must be virtual and requires IndexAttribute.

Analyzer

zeroformatteranalyzer

Supported Type

Primitive, Enum, TimeSpan, DateTime, DateTimeOffset, IList<>, IDictionary<>, IReadOnlyList<>, IReadOnlyDictionary<>, ILookup<>, byte[], ZeroFormatter.KeyTuple and there nullable.

T[], List<T>, Dictionary<TKey, TValue> is not supported type. You should use IList<>, IDictionary<> instead.

Dictionary's key is native serialized in binary, but has limitation of keys. Key can use Primitive, Enum and ZeroFormatter.KeyTuple for mulitple keys.

Serialize Dictionary/Lookup

TODO:...

for Unity

ZeroFormatter.Unity works on all platforms(PC, Android, iOS, etc...). But it can 'not' use dynamic serializer generation due to IL2CPP issue. But pre code generate helps it. Code Generator is located in packages\ZeroFormatter.Interfaces.*.*.*\tools\zfc\zfc.exe. zfc is using Roslyn so analyze source code, pass the target csproj.

zfc arguments help:
  -i, --input=VALUE          [required]Input path of analyze csproj
  -o, --output=VALUE         [required]Output path(file) or directory base(in separated mode)
  -s, --separate             [optional, default=false]Output files are separated
  -u, --unuseunityattr       [optional, default=false]Unuse UnityEngine's RuntimeInitializeOnLoadMethodAttribute on ZeroFormatterInitializer

Generated formatters must needs register on Startup. By default, zfc generate automatic register code on RuntimeInitializeOnLoad timing.

ZeroFormatter can not serialize Unity native types by default but you can make custom formatter by define pseudo type. For example create Vector2 to ZeroFormatter target.

#if INCLUDE_ONLY_CODE_GENERATION

using ZeroFormatter;

namespace UnityEngine
{
    [ZeroFormattable]
    public struct Vector2
    {
        [Index(0)]
        public float x;
        [Index(1)]
        public float y;

        public Vector2(float x, float y)
        {
            this.x = x;
            this.y = y;
        }
    }
}

#endif

INCLUDE_ONLY_CODE_GENERATION is special symbol of zfc, include generator target but does not includes compile.

Performance

Benchmarks comparing to other serializers.

ZeroFormatter is fastest(compare to protobuf-net, 2~3x fast) and has infinitely fast deserializer.

Architecture

Extensibility

ZeroFormatter can become custom binary layout framework. You can create own typed formatter. For example, add supports Guid and Uri.

public class GuidFormatter : Formatter<Guid>
{
    public override int? GetLength()
    {
        // If size is fixed, return fixed size.
        return 16;
    }

    public override int Serialize(ref byte[] bytes, int offset, Guid value)
    {
        // BinaryUtil is helpers of byte[] operation 
        return BinaryUtil.WriteBytes(ref bytes, offset, value.ToByteArray());
    }

    public override Guid Deserialize(ref byte[] bytes, int offset, DirtyTracker tracker, out int byteSize)
    {
        byteSize = 16;
        var guidBytes = BinaryUtil.ReadBytes(ref bytes, offset, 16);
        return new Guid(guidBytes);
    }
}

public class UriFormatter : Formatter<Uri>
{
    public override int? GetLength()
    {
        // If size is variable, return null.
        return null;
    }

    public override int Serialize(ref byte[] bytes, int offset, Uri value)
    {
        // Formatter<T> can get child serializer
        return Formatter<string>.Default.Serialize(ref bytes, offset, value.ToString());
    }

    public override Uri Deserialize(ref byte[] bytes, int offset, DirtyTracker tracker, out int byteSize)
    {
        var uriString = Formatter<string>.Default.Deserialize(ref bytes, offset, tracker, out byteSize);
        return (uriString == null) ? null : new Uri(uriString);
    }
}

You need to register formatter on application startup.

ZeroFormatter.Formatters.Formatter<Guid>.Register(new GuidFormatter());
ZeroFormatter.Formatters.Formatter<Uri>.Register(new UriFormatter());

One more case, how to create generic formatter. KeyValuePair<TKey, TValue> is also not suppoted type by default. Let's add support.

public class KeyValuePairFormatter<TKey, TValue> : Formatter<KeyValuePair<TKey, TValue>>
{
    public override int? GetLength()
    {
        return null;
    }

    public override int Serialize(ref byte[] bytes, int offset, KeyValuePair<TKey, TValue> value)
    {
        var startOffset = offset;
        offset += Formatter<TKey>.Default.Serialize(ref bytes, offset, value.Key);
        offset += Formatter<TValue>.Default.Serialize(ref bytes, offset, value.Value);
        return offset - startOffset;
    }

    public override KeyValuePair<TKey, TValue> Deserialize(ref byte[] bytes, int offset, DirtyTracker tracker, out int byteSize)
    {
        int size;
        byteSize = 0;

        var key = Formatter<TKey>.Default.Deserialize(ref bytes, offset, tracker, out size);
        offset += size;
        byteSize += size;

        var value = Formatter<TValue>.Default.Deserialize(ref bytes, offset, tracker, out size);
        offset += size;
        byteSize += size;

        return new KeyValuePair<TKey, TValue>(key, value);
    }
}

And register generic resolver on startup.

ZeroFormatter.Formatters.Formatter.AppendFormatterResolver(t =>
{
    if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
    {
        var formatterType = typeof(KeyValuePairFormatter<,>).MakeGenericType(t.GetGenericArguments());
        return Activator.CreateInstance(formatterType);
    }

    // return null means type is not supported.
    return null;
});

WireFormat Specification

Fixed Length Format

Fixed Length formats is ... (TODO:write description)

Enum is serialized there underlying type.

Type Layout
Int16 [short(2)]
Int32 [int(4)]
Int64 [long(8)]
UInt16 [ushort(2)]
UInt32 [uint(4)]
UInt64 [ulong(8)]
Single [float(4)]
Double [double(8)]
Boolean [bool(1)]
Byte [byte(1)]
SByte [sbyte(1)]
Char [ushort(2)]
Decimal [lo:int(4)][mid:int(4)][hi:int(4)][flags:int(4)]
TimeSpan [seconds:long(8)][nanos:int(4)]
DateTime [seconds:long(8)][nanos:int(4)]
DateTimeOffset [seconds:long(8)][nanos:int(4)]
Int16? [hasValue:bool(1)][short(2)]
Int32? [hasValue:bool(1)][int(4)]
Int64? [hasValue:bool(1)][long(8)]
UInt16? [hasValue:bool(1)][ushort(2)]
UInt32? [hasValue:bool(1)][uint(4)]
UInt64? [hasValue:bool(1)][ulong(8)]
Single? [hasValue:bool(1)][float(4)]
Double? [hasValue:bool(1)][double(8)]
Boolean? [hasValue:bool(1)][bool(1)]
Byte? [hasValue:bool(1)][byte(1)]
SByte? [hasValue:bool(1)][sbyte(1)]
Char? [hasValue:bool(1)][ushort(2)]
Decimal? [hasValue:bool(1)][lo:int(4)][mid:int(4)][hi:int(4)][flags:int(4)]
TimeSpan? [hasValue:bool(1)][seconds:long(8)][nanos:int(4)]
DateTime? [hasValue:bool(1)][seconds:long(8)][nanos:int(4)]
DateTimeOffset? [hasValue:bool(1)][seconds:long(8)][nanos:int(4)]

Variable Length Format

TODO:...

Type Layout Note
byte[] [length:int(4)][byte(length)] if length = -1, indicates null
String [length:int(4)][utf8Bytes:(length)] if length = -1, indicates null
KeyTuple [Item1:T1, Item2:T2,...] T is generic type, T1 ~ T8, mainly used for Dictionary Key
KeyTuple? [hasValue:bool(1)][Item1:T1, Item2:T2 ...] T is T1 ~ T8
FixedSizeList [length:int(4)][elements:T...] represents IList<T> where T is fixed length format. if length = -1, indicates null
VariableSizeList [byteSize:int(4)][length:int(4)][elementOffset...:int(4 * length)][elements:T...] represents IList<T> where T is variable length format. if length = -1, indicates null

Object Format

Object is variant of Variable Length Format that can define user own format. Layout is [byteSize:int(4)][lastIndex:int(4)][indexOffset...:int(4 * lastIndex)][Property1:T1, Property2:T2, ...]. If byteSize = -1, indicates null.

Dictionary Format

| Dictionary | [length:int(4)][(key:TKey, value:TValue)...] | represents IDictionary<TKey, TValue>, if length == -1, indicates null | | MultiDictionary | [length:int(4)][(key:TKey, elements:List<TElement>)...] | represents ILookup<TKey, TElement>, if length == -1, indicates null | | LazyDictionary | [byteSize:int(4)][length:int(4)][buckets:FixedSizeList<int>][entries:VariableSizeList<DictionaryEntry>] | represents ILazyDictionary<TKey, TValue>, if byteSize == -1, indicates null | | DictionaryEntry | [hashCode:int(4)][next:int(4)][key:TKey][value:TValue] | substructure of LazyDictionary | | LazyMultiDictionary | [byteSize:int(4)][length:int(4)][groupings:VariableSizeList<VariableSizeList<GroupingSemengt>>] | represents ILazyLookup<TKey, TElement>, if byteSize == -1, indicates null | | GroupingSegment | [key:TKey] [hashCode:int(4)][elements:VariableSizeList<TElement>] | substructure of LazyMultiDictionary

Struct Format

Struct is variant of both Fixed Length or Variable Length format. If all properties... TODO:...

EqualityComparer

TODO:...

https://github.com/neuecc/ZeroFormatter/blob/master/src/ZeroFormatter/Comparers/WireFormatEqualityComparers.cs https://github.com/neuecc/ZeroFormatter/blob/master/src/ZeroFormatter/Comparers/KeyTupleEqualityComparer.cs

Cross Plaftorm

Currently No and I have no plans. Welcome to contribute port to other languages.

Author Info

Yoshifumi Kawai(a.k.a. neuecc) is a software developer in Japan.
He is the Director/CTO at Grani, Inc.
Grani is a top social game developer in Japan.
He is awarding Microsoft MVP for Visual C# since 2011.
He is known as the creator of UniRx(Reactive Extensions for Unity)

Blog: https://medium.com/@neuecc (English)
Blog: http://neue.cc/ (Japanese)
Twitter: https://twitter.com/neuecc (Japanese)

License

This library is under the MIT License.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.