Unreal Engine... How did they do it?

Started by
20 comments, last by dougbinks 11 years, 5 months ago
Hiya,

I've recently been shown a demo reel of the Unreal Engine 4 (clicky).

At the very end, while running around in the game world, the demonstrator changes some of the source code in Visual Studio. While he continues to run around the code is recompiled, and suddenly the changes take effect in the game world - without even restarting the simulation.

This struck me as pretty amazing (more so than the fancy graphics laugh.png) and I have no idea how they do it. Somehow they are able to replace running binary code in-memory, without the machine exploding. If anyone is interested, it starts around 9:45.

It's far beyond me to try and make anything like this but I'm fascinated - does anyone have an inkling how it works?
Advertisement
My guess is they are changing and recompiling a shared library, which can be dynamically loaded and unloaded. The details are OS specific.

In Windows, shared libraries are called "dynamic-link libraries" (DLLs).
That was my guess, but I'm not sure it would be easy.

If you pull the rug from under a DLL like that surely you'll invalidate any pointers to objects within it, the state of those objects, etc. They must have designed the DLL very carefully, and/or serialise all the objects to another part of memory and then deserialise them back to the new library when it's loaded. It sounds terribly complicated to me.

Thanks! Interesting stuff.
I stumbled upon this blog a while back which might be of interest: http://runtimecompiledcplusplus.blogspot.be/
They even mention the similarities between their technique and the technique used by UE4

I gets all your texture budgets!

N.B. visual studio has contained an "edit and continue" mode built into it for a long time. Edit your project properties to include "edit and continue" debugging information (on top of the regular debug info), and when you hit a breakpoint in the debugger, you can make some changes and press continue.
If you pull the rug from under a DLL like that surely you'll invalidate any pointers to objects within it, the state of those objects, etc.
Most objects aren't allocated "in a DLL", they're allocated within the address space of your program.
As long as you don't change the layout of those object's classes when reloading the code, they'll continue to be valid.

If you pare back all the abstractions, all our game code is, is a bunch of data-structure definitions, and procedures that can transform those data-structures. There's no reason why you shouldn't be able to make your transforms re-loadable (and it's a huge productivity boost if you do so!).

Making run-time changes to your structures is more complicated, and VS "edit and continue" simply doesn't support this. Radikalizm's awesome link seems to serialize objects who's structures have changed, so they can be re-created after re-loading the code. I'm definitely going to have a good look at how that project works!

I stumbled upon this blog a while back which might be of interest: http://runtimecompil...us.blogspot.be/
They even mention the similarities between their technique and the technique used by UE4

Amazing!

...

Helpful explanation, thanks.

That library does look identical to Epic's implementation - they might be one and the same. I've just given their test code a run through and it works as advertised, I'll definitely be having a closer look into how exactly they're doing it. They've even geared the sample code up for integrating into an existing project.

I've definitely learnt some useful and interesting things today, thanks for your replies laugh.png

All the best.

[Edit] It looks like they do indeed serialise objects back into the recompiled module.

Making run-time changes to your structures is more complicated, and VS "edit and continue" simply doesn't support this. Radikalizm's awesome link seems to serialize objects who's structures have changed, so they can be re-created after re-loading the code. I'm definitely going to have a good look at how that project works!


When I first read about their technique I was quite skeptical about it and I assumed they weren't telling the entire truth, but right now this seems to be the real deal.
By design the technique itself is quite elegant too, and I can see a massive productivity boost in a lot of programming fields once this gets more widespread

I still have to make some time to play with their demo code myself though, but if this works without any major issues I'll probably be integrating this into my own development code - if possible.

I gets all your texture budgets!

Glad you've found our project useful!

We don't know if the Unreal implementation is related to ours - it's possible that after our presentation in 2011 at the AI GameDev conference in Paris they found out about it but it's also possible they came up with a similar solution themselves.

On the subject of modules and memory, you do need to ensure you either have virtual destructors or something similar as objects are allocated on each modules heap.

Glad you've found our project useful!

We don't know if the Unreal implementation is related to ours - it's possible that after our presentation in 2011 at the AI GameDev conference in Paris they found out about it but it's also possible they came up with a similar solution themselves.

On the subject of modules and memory, you do need to ensure you either have virtual destructors or something similar as objects are allocated on each modules heap.


Ahh perfect, now I can say this in person: Awesome work!

If I can get this to work nicely with my development code you might have saved me quite some production time smile.png
Needless to say I'll be following your project closely

I gets all your texture budgets!

N.B. Not trying to put a damper on these awesome ideas here, just pointing out some pitfalls to be aware of:
Two issues with supporting changes to data-structures (which implies serialization/deserializaiton) is that many game engines are (1) very specific about how their utilize their RAM, and (2) also very limited in the amount of RAM they can use.

(2) isn't an issue on PCs, and console dev-kits usually have double the retail-version's amount of RAM, so it's likely only a minor problem affecting some of your SKUs.

(1) complicates matters though. Even with a general-purpose allocator, when freeing 10,000 objects and then allocating 10,000 slightly larger objects, it's possible that the original allocations aren't contiguous (so they become little bubbles in your address space), and the new allocations are too big to fit in those "bubbles". This fragmentation of RAM can have the same effect as if you'd simply leaked the original allocations (until you free the used-space around them, probably at the end of the level), which can make it much more likely to hit the RAM limits from (2).

Further, optimised engines don't use global, general-purpose global allocators (i.e. new/malloc) for everything. To construct a certain kind of object, you might need to specify which allocation area to construct it in, and you might not be able to free that allocation at a random time (e.g. perhaps bulk-deallocation of the whole area is used).

For example, when allocating an object in our renderer, depending on which systems are likely to access it and it's required lifetime, the user will specify 1 allocator out of a possible 4 different stacks and 4 different heaps that could be used. i.e. the same object could be constructed within 8 different areas of RAM, but choosing the right one is important based on usage.
If the object is allocated in one of the stack areas, it's can't be freed at will; it's lifetime is bound to the lifetime of the stack. Reallocating with a larger size means 'leaking' the original and adding a new object on the top of the stack (which there might not be space for). Alternatively, it means serializing the entire stack and re-allocating the whole thing from the bottom-up to avoid these holes (and then patching any other objects that held pointers into this stack).

These aren't un-solvable problems, but a class-layout-patching solution that's applicable for every engine would have to be very flexible in regards to allocations, and may end up having to serialize/deserialze hundreds of megs of data per 'reload'.

That said, gameplay code is usually less performance-critical than engine code, so maybe you just don't support this feature on your critical engine systems ;)

This topic is closed to new replies.

Advertisement