Flax Editor and C# Scripts reloading
Welcome to the newest Flax Facts! It’s time for another not/a little/very interesting blog post. You choose how interesting it is. But before, let’s dive into one of the coolest Flax Editor features which is C# scripts hot-reloading!
Flax Engine developers write game scripts in C# language. Flax Editor generates the required solution file and project so with installed Flax.VS plugin it’s very easy to write new code. However, there is one important thing, what to do when a developer adds/removes a chunk of code? How to apply the changes?
Firstly let’s look at the Unity which implements a very similar concept for game scripting. Unity Editor after detecting the change, calls the scripts compilation and performs full C# reload. This means they firstly serialize editor state (with ScriptableObjects and so on) and then unload C# domain. Later new assemblies can be loaded and state restored. I would call it a full reload as all the static fields and objects are released and the managed world gets a full reset.
Let’s think which are good and bad sides of Unity’s solution? Firstly, it’s very safe and clean. No missing objects or references will survive after domain unload. I’m not sure if they’re also reloading editor assemblies but probably yes because the whole domain is unloaded and in fact, anyone who ever used Unity Editor is already familiar with this long and annoying stall on scripts reload when the editor is unusable. That’s definitely a very bad thing which gets even worse in large projects. At Unity, I think, they don’t know about using asynchronous data processing and doing stuff in the background in general (see assets converting pupup when you change the project target platform).
Hot scripts reload
Now, it’s time to present our solution for reloading C# scripts.
- Scripts compilation starts in a background
- Flax serializes all the scene objects (actors, scripts)
- References to old objects/types are cleared
- Flax calls Garbage Collection (and waits for the finalizers)
- Flax unloads Assembly.dll and Assembly.Editor.dll
- Mono clears the types and classes
- Debugger symbols cache is cleared
- Dynamic methods are removed
- Flax loads new assemblies
- Flax restores the scenes and objects
The beginning is quite similar when Flax Editor detects the source change it calls the scripts compilation (which is done in a background so the user can still edit game objects/assets). After compilation Flax serializes all the scene objects state (actors, scripts) and starts to reload the user assemblies. However, we don’t unload the whole domain. Only user assemblies are reloaded. It’s not possible in .Net but we use custom Mono fork. Again, it’s not a standard thing to unload single .dll file in C# but we’ve added some changes to Mono and it runs pretty good. But the best thing about it we don’t reload editor so the hot-reload is super fast and super hot!
To show you how fast it is here is a chunk of engine log file where you can see all the Flax actions being performed during the scripts reload. In this example, we have a small scene with some scripts and it takes 154ms to reload it. Pretty fast huh? And it scales to big projects.
[ 00:00:23.858 ]: [Info] Starting scripts compilation... [ 00:00:23.859 ]: [Info] Command: C:\Flax/Source/Bin/Tools/Flax.Build.exe "C:\Flax/Source/Bin/Cache/BuildLog 0 2 C:\Flax/Source/Bin/Flax Dev.sln C:\Flax/Source/Bin/Cache/bin/Assembly.dll C:\Flax/Source/Bin/Cache/bin/Assembly.Editor.dll" [ 00:00:24.136 ]: [Info] Scripts compilation time: 0.28s [ 00:00:24.136 ]: [Info] Flax.Build result: 0 [ 00:00:24.154 ]: [Info] Scripts reloading start [ 00:00:24.167 ]: [Info] Changing editor state from EditingSceneState to ReloadingScriptsState [ 00:00:24.167 ]: [Info] Saving scene Scene to 'C:\Users/Wojtek/AppData/Local/Temp/439a3238/13b09287.scene' [ 00:00:24.228 ]: [Info] Scene saved! Time 61 ms [ 00:00:24.230 ]: [Info] Cleanup graph for scene 'Scene' [ 00:00:24.230 ]: [Info] Debug Log: OnDisable [ 00:00:24.239 ]: [Info] Start user scripts reload [ 00:00:24.276 ]: [Info] Unloading managed assembly 'Game.Editor' (is reloading) [ 00:00:24.283 ]: [Info] Unloading managed assembly 'Game' (is reloading) [ 00:00:24.285 ]: [Info] Assembly Game loaded in 1ms [ 00:00:24.286 ]: [Info] Assembly 'Game' scanned for custom editors in 1 ms [ 00:00:24.286 ]: [Info] Assembly Game.Editor loaded in 0ms [ 00:00:24.286 ]: [Info] Assembly 'Game.Editor' scanned for custom editors in 0 ms [ 00:00:24.286 ]: [Info] End user scripts reload [ 00:00:24.286 ]: [Info] Loading temporary scenes [ 00:00:24.286 ]: [Info] Loading scene from file. Path: 'C:\Users/Wojtek/AppData/Local/Temp/439a3238/13b09287.scene' [ 00:00:24.287 ]: [Info] Loading scene... [ 00:00:24.287 ]: [Info] Loading 0 lightmap(s) [ 00:00:24.293 ]: [Warning] Scene 'Scene:13b0928743c86288f6bb66b5350bb7c8', has been loaded but not initializated. [ 00:00:24.294 ]: [Info] Created graph for scene 'Scene' in 1 ms [ 00:00:24.295 ]: [Info] Scene loaded in 8 ms [ 00:00:24.295 ]: [Info] Prepare scene objects [ 00:00:24.306 ]: [Info] Debug Log: OnEnable [ 00:00:24.308 ]: [Info] Scripts reloading end. Total time: 154ms [ 00:00:24.308 ]: [Info] Changing editor state from ReloadingScriptsState to EditingSceneState
Pros and cons
The last thing to discuss is good and bad sides of our solution. Of course, some may say it’s cool but for others, it will be just a strange way. Here is a quick sum up:
- It’s blazing fast
- Editor state is untouched – undo and workspace stays the same
- Helps with rapid game development
- Scalable solution
- Unsafe – user need to free event handlers and use static code with care
- May result in crashes
- Doesn’t simulate actual game reload
- Requries to use custom Mono fork
That’s all for today. I wonder what’s are your thoughts about reloading C# scripts? Which solution is better? I hope you find this blog interesting. Please let me know! Bye 🙂