Sunday, 3 March 2019

Experimenting with CoreRT

I hope you like acronyms.

As work on porting [REDACTED] continues, I've been putting more thought into distribution.  Now that XAGE uses .NET Core, we have the option to publish 'standalone' executables, which means there is no requirement to have a version of the .NET framework installed on the end user's machine.  This is a good thing, though results in a version of .NET Core being bundled in with the final executable, which at the moment means lots of individual files and a much larger overall size.

There is a mechanism being developed to allow these individual files to be essentially be bundled together and extracted to a temporary location at runtime, though I'm not convinced that this is the best approach.

A more interesting prospect is leveraging CoreRT in order to compile everything ahead-of-time (AOT) into a native executable.  This comes with some benefits:

  • The final standalone executable is much smaller and completely self-contained.
  • The startup time is much quicker as no just-in-time (JIT) compilation is required.
  • General performance is better.
  • Decompilation of the code is much harder with native executables compared to .NET. 

At the cost of a few downsides:

  • The process of the AOT compilation & linking to remove unused code means that you need to provide some details on items not to remove in the form of an XML file.
  • One of FNA's selling points is that you can package your game with MonoKickstart in such a way that it will work for x86 and x64 Windows, Mac and Linux.  Using CoreRT will mean that separate packages need to be prepared per platform. 

The CoreRT project is still in early preview, but has made a lot of progress in the last few years.  In the process of trying to get it work with XAGE, I came across a major stumbling block:  CoreRT does not currently support XmlSerialization and Protobuf-net - both of which are serialization techniques used by XAGE.  The reason for this is that CoreRT does not support Reflection.Emit, which generates Intermediate Language (IL) which is JITed into native code.

There are some changes on the way that should make both viable with CoreRT, but in the meantime I wanted to explore some different serialization techniques, which is when I discovered Biser.  This little-known library allows you to generate serialization C# classes AOT that you can include in your final executables or libraries, meaning no run-time reflection is required (not dissimilar to something I'd first tried back in 2010).  I was able to rewrite this and include it in the XAGE workflow such that it could be used as the single serialization method within the engine runtime:


And voilà, we have our minimum viable game runtime - here for Windows x64:


Where the executable is under 24MB and there are only 11 files in total:

  • Four content files specifically for the XAGE game (game.zip, graphics.zip, audio.zip and voice.zip - the latter two of which are optional)
  • Six native .dlls in the x64 directory - five required for FNA (FnaLibs) and one for Dear ImGui, which you probably wouldn't need as part of the Release build anyway.
  • Strictly speaking I should also include FNA.dll.config, but it seems to work fine without.

I like this a lot, as we get the bleeding edge functionality & performance from .NET Core while also producing neat and tidy native distributables.

I'm not sure whether I'll stick with my fork of Biser (unimaginatively named Xiser) or return to Protobuf-net once it matures further and is able to support CoreRT.  I have a lot more confidence in Marc Gravell's ability to maintain a robust & feature rich serializer than my own.  But it's good to have options.

2 comments:

Cyrille Lagarigue said...

Hi great post, I also ported my game to CoreRT and here are some insights:
. Right now, CoreRT does not support publishing a x86 executable, sor we are stuck with x64 (not a lot of x86 machines left anyway if you trust Steam statistics).
. XmlSerialization is possible with the right tags in the rd.xml, but it is still a pain to make it work, so I ended up writing a simple serializer/deserializer for my config file (the only one that used xml).
. I myself use ProtoBuf in my game, and I managed to make it work with CoreRT. You just have to generate a dll before building your game, with TypeModel.Build. It is a little bit what you did, you just generate a Dll instead of generating code.

Clarvalon said...

Thanks for the insight - you got further than I did :) I saw that Marc was going to rewrite how protobuf handled building AOT serializers, and that the old ones were tricky to use, so decided to hold off. As a quick workaround, Biser worked pretty well!