17 Feb 2017

Unity Protocol Buffers

Time for another “ah, crap – better put something up here before heading to GDC”-post. And since I did some re-tinkering with the integration of protobuf in Behave late last year, some sharing on that topic seemed in order.

So yes, stuff is happening with Behave – no abandonment has taken place. On the contrary I have been working on several, long-running, feature branches. I’ll get around to merging some of those in and doing another release. More on that soonish.

Anywho, last I checked, protobuf-net was still a more complete solution than the younger protobuf for C# one, so I still stick with protobuf-net and can therefore only talk about that here. However it looks like the latter is seeing more rapid development, so I’ll probably review my choice when next I need to tinker with the integration.

More interestingly, some of the initial people behind protobuf have since gone and built the new and shiny Cap’n Proto. This looks to be even more powerful than its predecessor, but at time of writing it is still not as mature or implementation-rich as protobuf. Critically for the context of this post, there is no C# implementation yet. Cool stuff, though – worth keeping an eye on.

Overview

So what is the Protocol Buffers project? Succinctly, it is a compact binary serialisation format with very fast cross-platform and -language implementations. Super useful when you need to quickly move some data between memory and some other location – a file / a network peer / whatever.

It was first designed, implemented, and maintained by Google for communications between network peers on their internal network. Since then, it has been implemented in a number of different languages – including the protobuf-net implementation, built and maintained by Marc Gravell.

Serialisation use in Behave has gone through a couple of distinct phases:

  1. .net binary serialiser for asset.
  2. protobuf-net for asset.
  3. protobuf-net for asset and remote debugger protocol.
  4. JSON for asset, protobuf-net for remote debugger protocol.

Additionally I’ve used protobuf-net for runtime saving or build pipelines on various client projects.

Aside from speed and compression, protobuf-net vs. the .net binary serialiser was also an escape of the frustrating lack of support for versioning or even simple structural refactors of serialised types. The switch to JSON for the .behave asset files of-course came when I decided that merge-ability could be a fun thing to support.

On top of this general migration, I also went through a couple of different protobuf-net integrations – as my use cases and requirements changed. It just so happens that this gave me full coverage of the three approaches supported by protobuf-net, so no need for extra research before writing this post. Win!

Integration

For serialisation to work, you need a a serialiser and a schema. Protobuf-net offers a couple of ways for you to provide those:

  1. [Attribute] markup with runtime generated serialiser.
  2. [Attribute] markup with pre-generated serialiser.
  3. .proto file description with pre-generated serialiser.

I did this list in order of simplicity, which also happens to be the order in which I switched through them in the integration with Behave.

The first option is very .net esque and protobuf-net is indeed compatible both with its own serialisation attributes and the more general-purpose .net serialisation attributes. However since your interest here is in a Unity context, I’m sure that you have already spotted the problem with this approach.

Given that my initial use for protobuf-net was just for asset serialisation (which is editor-time only), I had no problem with the serialisation solution relying on JIT compilation in order to construct the serialiser at runtime. However as soon as I expanded my use case to include the runtime debugger, relying on JIT would mean not supporting the debugger on AOT platforms like iOS and consoles. Further, as Unity continues transitioning to their IL2CPP solution, you’re looking at a future where most will want to do AOT on all platforms.

So when introducing the remote debugger (previous debugger implementation was just 100% in-editor), I started to pre-generate the serialiser. This entails feeding your compiled assembly with [Attribute] markup to the protobuf-net precompile tool, which in turn generates an assembly with the serialiser type. For an example of how I used to do that, here’s a snippet of perl.

Extraction

Everything works! Win, right? Well… as I was expanding debugger support to non-C# targets and doing a general code cleanup, I got increasingly annoyed by having serialisation implementation detail in my general data type code. So I started looking at how protobuf-net supports the standard protobuf .proto format for schema definition.

As things stand, it takes a bit of work – specifically this work – to get going, but once there it is solid. In stead of using the precompile tool, you need to build and run the protogen tool from the protobuf-net repository. If you’re on Windows, things may just work straight out of the gate, but you may want to see what is in my patch anyway.

So how does this work? Well, the input is no longer [attribute] marked assemblies, but in stead a .proto definition file. You can find great detail on that in the general protobuf documentation, but you may want to consult the protobuf-net docs for implementation specifics/limits. Also, the output is not an assembly, but simply a C# file.

Keep in mind that protogen relies on the protoc tool from the general Google protobuf tools. I fetched this from homebrew as the “protobuf” package.

Again I have a snippet of perl to illustrate use – as well as an example .proto file. In this case the Behave debugger protocol definition.

Using this approach, I now have a nicely separated codebase and no duplication of schema definition between the C# debugger runtime and others. One trick remains though…

Object pooling

A mildly active Behave debugger session involves a lot of messages. I have very little interest in blowing up the garbage collector by constantly instantiating new messages and leaving them to get collected. So how do we integrate an object pool setup with the generated protobuf-net serialiser?

While not exactly as pretty as I would like, my answer is making use of the partialness of the generated types – in order to add a static pool and -constructor to hook it up.

And that’s pretty much what I’ve got. If I missed something or you have related questions, feel free to ping me and I’ll try to update this post when I can.

Unity Protocol Buffers
Behave 2.7
Learn
Behave 2.6
Trusted Gear
Mad Mash Versioning
Behave 2.5
Behave 2.4
Co-very-routine
Construct
The Engine Wars: Numbers
GDC 14: The Quest For Fun
Moving in Unity
Behave 2.3
Unity and .net assemblies
Behave 2.2
ReView
Behave 2.1
Behave 2.0
Unity Hacks: Dual sticks
Unity Hacks: Cameras
Unity Hacks: Touch gestures
OnRenderTextureGUI
Unite 13 video "Unity Hacks" available
The implicit local network interface
Talks and progress
Five years of Unity expertise looking for contracts
Automagic Unity Android Java gadget OF DOOM!
Invading Planet from your couch
Mountain Lion and laggy bluetooth and duct-tape
Unite 12 video and new videos section available
Asia Bootcamp videos now available
Path is now MIT licensed
Behave 1.4 released
So I've been a bit busy lately
Behave 1.3 released
IGDA Unity SIG slides
Second Unity IGDA SIG this evening: Scene construction and AI
First IGDA Unity SIG this evening
Alternative licensing available
Pathfinding in two lines
Path 2 released
Assembling and assimilating
Path 2 intro screencast
Path 2 beta release for GGJ
AIgameDev master class video now online
Expanding beta
Behave AIgameDev master class public stream
Behave master class on open AIgameDev stream tomorrow
Interview with AIGameDev
New video: From tree to code
Issue tracking on github Behave release project
IT University Copenhagen Unity course completed
IT University Copenhagen Unity course files Thursday
CPH IT University Unity course files
Behave 1.2 released
Video: Behave - starting from scratch
Behave runtime documentation updated
Behave 1.1 released
FAFF cleanup: Sketch
Building a menu of delegates and enums
Pick me! Pick me!
Optimising coroutine yielding in C#
Downloading the hydra
New license of Path: GPL
CopyInspector
Magnetic
GUI drag-drop
Logging an entire GameObject
I bet you can't type an A!
Where did that component go?
UnitySteer
New and improved: Behave 1.0 released
Behave 0.3b and unity 2.5
Behave 0.3b hotfix
Path tutorial video available
Path 1.0 launched!
Continued community tutorials
Community tutorial
New tutorial
First tutorial available
Behave 0.3b
unite '08 open-mic session
Behave 0.2b
Behave 0.1b
Behave pre-release
Path beta 0.3b
Path beta 0.2b
Path beta 0.1b
Path pre-release