Hot-reloading code with virtual tables

I've managed to achieve some great success in my little game with hot reloading in a similar way Casey does it in HMH.
However I'm not the "handmade everything" type of guy and my game requires 2d physics so I naturally leaned towards box2d.

Now the problem with box2d is that the source is not suited to be dynamically linked (they even state so in the FAQ). It uses a bunch of static variables internally (which do not work with hot reloading) which I was able to factor out into the state. Now the next problem is that it also uses virtual functions in a lot of places and refactoring those sounds like a bit too much. Virtual functions often break when hot-reloading (or rather pointers to them in the existing instances become obsolete).

Is there any recommended solution for solving this type of thing? Maybe some pointer-to-vtable patching?
If you don't care about API boundaries/layering and just want it to work, then compile the Box2d implementation into the non-reloaded code (i.e. the platform layer in HMH). Your Box2d usage code can live in your reloaded-code just fine.

You may need to pass '-rdynamic' (assuming Clang/GCC) to the compiler when compiling the non-reloaded code.

You might be able to compile Box2d into an object file once (i.e., don't build that every time you build reloadable), and then link it in when making your reloadable object... (I'm not sure if the linker will try to relocate the symbols in that case)... You might also be able to compile the Box2d implementation into it's own dynamic object, and load that one along side your reloadable object (just don't reload the Box2d implementation)

This is one of the hallmark drawbacks of using virtual functions

Edited by David Butler on
Not sure about object files, but you could simply compile box2d to its own shared library (.dll file). Then it will work fine across reloads.
Not sure about object files, but you could simply compile box2d to its own shared library (.dll file). Then it will work fine across reloads.

That was my first option but it seems that it would require modifying box2d source code in a way I don't fully understand yet. Is just marking everything with extern "C" enough for that?

If you don't care about API boundaries/layering and just want it to work, then compile the Box2d implementation into the non-reloaded code (i.e. the platform layer in HMH). Your Box2d usage code can live in your reloaded-code just fine.

I tried that but couldn't accomplish it for some reason, I'm using cmake and not sure how exactly would that be done. I'm able to build a static library/include source code in the platform layer, but not link it in the game layer.
No need to modify anything in source code. Just instruct linker to export all symbols.

They are not "recommending" dynamic library probably because its harder for them to distribute one. Because of C++ API. C++ ABI is not compatible between compilers or sometimes even between different versions of same compiler.

But as long as you control what compiler you use to create dll file and use same compiler to use it - then you'll be fine.

Edited by Mārtiņš Možeiko on
Oh damn, thanks everyone. I didn't need to export all symbols since I'm not building on windows right now.

It turned out I could link box2d dynamically all this time, it was just my mistake of not reading the cmake manual or something.

1
2
add_library(box2d SHARED ${box2d_directory})
target_link_libraries(game ${SDL2_LIBRARY} ${SDL2_IMAGE_LIBRARY} ${SDL2_MIXER_LIBRARY} ${OPENGL_LIBRARIES} ${box2d})


Should have been just

1
2
add_library(box2d SHARED ${box2d_directory})
target_link_libraries(game ${SDL2_LIBRARY} ${SDL2_IMAGE_LIBRARY} ${SDL2_MIXER_LIBRARY} ${OPENGL_LIBRARIES} box2d)


Which is why I was getting linker errors! Everything seems to work now.

Box2D FAQ stating they do not support dynamic linking kinda threw me off the track, since I'm not very experienced in the whole C++ thing, been only learning it for a bit than one month haha.

Edited by Kirill Artemov on
If you do find yourself fiddling with Box2D's internals or wanting to hot-reload other code that uses virtual method tables, you can always patch the pointer in each instance using a special constructor and placement new:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Use a dummy type if you don't want the no-op constructor to collide with real ones.
static const struct FixVirtualMethodTable {} FIX_VIRTUAL_METHOD_TABLE = {};

class IFoo {
 public:
  explicit IFoo(const FixVirtualMethodTable _) {}

 public:
  virtual void foo() = 0;
};

fread(blob, 1, size_of_blob, serialized);
new (blob) IFoo(FIX_VIRTUAL_METHOD_TABLE);


PhysX, does this for example. I believe Bungie did this when dealing with Havok and their tag system (they pretty much malloc + fread everything in). So not pretty, but not unprecedented.

Edited by Michael WIlliams on Reason: Fixed code example.
mtwilliams
If you do find yourself fiddling with Box2D's internals or wanting to hot-reload other code that uses virtual method tables, you can always patch the pointer in each instance using a special constructor and placement new:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Use a dummy type if you don't want the no-op constructor to collide with real ones.
static const struct FixVirtualMethodTable {} FIX_VIRTUAL_METHOD_TABLE = {};

class IFoo {
 public:
  explicit IFoo(const FixVirtualMethodTable _) {}

 public:
  virtual void foo() = 0;
};

fread(blob, 1, size_of_blob, serialized);
new (blob) IFoo(FIX_VIRTUAL_METHOD_TABLE);


PhysX, does this for example. I believe Bungie did this when dealing with Havok and their tag system (they pretty much malloc + fread everything in). So not pretty, but not unprecedented.


won't work if any of the fields have their own default constructor they still get called with that constructor
Absolutely. You have to account for that.