Phase 0: Motivation and goals
Today, I want to take you on the journey of how and when Live++ started, what the motivation for it was, and which goals I set when I started working on it.
Motivation
When I started building the Molecule Engine, everything was developed with hot-reloading in mind right from the start. As some of you maybe remember, Molecule offered hot-reloading for each and every asset in the engine, ranging from shaders and textures to meshes exported directly from Maya.
After just a few days of hot-reloading shaders all day, I knew that I wanted to have the same kind of efficiency when writing C++ code as well. So I built myself a system that enabled me to use C++ like a kind of scripting language, with C++ "script" code living in micro-DLLs that could be hot-reloaded by the engine.
Working in this kind of codebase quickly became very transformative. It is hard to describe for people who have never experienced this themselves, but it makes you feel as if you've been programming with handbrakes on all your life, when somebody hands you the keys to a shiny new Lamborghini and suddenly you're going full speed.
While that approach is certainly feasible and you can ship games with it on several platforms, including consoles and mobile, it has one obvious drawback: your code needs to be built around this hot-reloading facility from the very start.
So monolithic executables that don't use any kind of shared libraries are out of the question with this approach. I remember there being a comment on my blog that said:
Would also be great if you eventually released a standalone component for this.
To which I responded:
Releasing a stand-alone product for something like this is pretty hard, I would say. Why? Because it is a very intrusive process, and depends a lot on how the engine/codebase using it is structured.
Turns out I was not entirely wrong back then.
Goals
Even though I continued working on completely different things for a few years, I could kind of never stop thinking about how amazing a generic hot-reload system for C++ would be. After all, it was something I wanted to have for myself, especially when doing freelance work on foreign codebases.
So in 2016, after a few months of R&D, dabbling around with PDB files a lot, dissecting object files and trying to load simple code from them directly, I got a vague idea of how a generic tool could be pulled off - the word vague being load-bearing here.
With this, I sat down and thought about which properties a generic tool must bring to the table for it to be adopted by others.
Genericity
This was and still is the most important one for me. It's not very realistic to expect people to adopt a tool that could work very well for them, if only they changed the 2+ million lines of C++ code they had accumulated over the last decade. That's simply not going to happen.
In other words, the tool needs to work out of the box, with no or just minimal action required before the user can see results. In my opinion, that is one of the reasons why Superluminal is so successful. It gives you that It Just Works experience: all you have to do is download, install, profile.
Project setup, build setup and toolchain agnostic
In a similar vein, the tool should not care about how a user's C++ project is set up or built. It should not matter whether people use Visual Studio, VS Code, CLion, Rider, CMake, ninja, Bazel, FASTBuild, or Incredibuild. If possible, it also should not matter which toolchain was used to build the code.
Similarly, it also should not matter how the project is structured.
- A single executable?
- Several hundred shared libraries?
- A bunch of static libraries linked into executables and shared libraries?
It should be possible to hot-reload everything you have the C++ source for.
Application agnostic
As a logical continuation, it should be possible to hot-reload code in applications that know nothing about hot-reload, as long as the necessary information and source code are there.
As an example, you can use Live++ today to hot-reload C++ code that was compiled into a DLL, with the DLL being loaded and used inside the Unity game engine. Unity knows nothing about C++ hot-reload, yet this setup works. The same is true for e.g. plug-in DLLs loaded into Maya.
Native performance
Since my runtime-compiled C++ code setup I developed for Molecule was running native code at native performance, I never wanted to offer anything other than compiled code. On top of that, interpreters and JIT-compilation were not my areas of expertise (and still aren't).
Externalize as much as possible
Coming from a GameDev background, I knew that engineers in this sector care a lot about where and when memory is allocated, when threads are spawned, and how resources are used in general. Letting the tool run wild and allocate several hundred megabytes of memory for my own data structures in the user's process wasn't going to fly.
Multi-platform
In terms of Live++'s architecture, being multi-platform was not something I planned for from the start. It was more what I had been dreaming of in case I could successfully ship Live++ for Windows. Most games were and still are on Windows (or at least have a Windows build that is used during development), so that was my top priority.
Expectations
I cannot remember how long I thought Live++ would take me to develop and ship back then, but I certainly underestimated everything. Make no mistake: it was one of those classic "how hard can it be?" moments and I had no idea what I had signed up for.
With the stage being set, next time we are going to start looking into how the above goals were achieved. We are also going to be a lot more technical from this point forward.