Issues with live code reloading and third party library

Hello,

I'm currently creating my own 2d fighting game and I've implemented things to mostly match Casey's project as far as the live code reloading and things go (up to about day 26). From there I've added in a third party animation library, spine, to help with my animations. I've gotten everything to work prior to adding in some actual animations as I have this library calling my own allocator and using my game memory block.

The issue I'm having is sometimes an exception is thrown on this line of the spine library upon building and trying to reload my gamecode dll:

1
2
3
4
void spTimeline_apply (const spTimeline* self, spSkeleton* skeleton, float lastTime, float time, spEvent** firedEvents,
      int* eventsCount, float alpha, spMixBlend blend, spMixDirection direction) {
   VTABLE(spTimeline, self)->apply(self, skeleton, lastTime, time, firedEvents, eventsCount, alpha, blend, direction);
}


The 'VTABLE' macro expands to:

1
2
/* Gets the vtable for the specified type. Not type safe, use with care. */
#define VTABLE(TYPE,VALUE) ((_##TYPE##Vtable*)((TYPE*)VALUE)->vtable)


Again, my code reloading does still work about 50% of the time but when it does crash it almost always falters here. Is anyone able to help me work through why this is spotty? My current gamecode looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
extern "C" void
GameUpdate(Game_Memory* GameMemory, Platform_Services PlatformServices, Game_Render_Cmds RenderCmds, 
                    Game_Sound_Output_Buffer* SoundOutput, const Game_Input* GameInput)
{
    Game_State* GameState = (Game_State*)GameMemory->PermanentStorage;
    GlobalGameState = GameState;

    const Game_Controller* Keyboard = &GameInput->Controllers[0];
    const Game_Controller* GamePad = &GameInput->Controllers[1];

    if(!GameMemory->IsInitialized)
    {
        GameState->DynamAllocator.MemRegions[SPINEDATA] = CreateRegionFromGameMem(GameMemory, Megabytes(10));
        InitDynamAllocator(&GameState->DynamAllocator, SPINEDATA);

        GameState->Atlas = spAtlas_createFromFile("data/spineboy.atlas", 0);
        GameState->SkelJson = spSkeletonJson_create(GameState->Atlas);
        GameState->SkelData = spSkeletonJson_readSkeletonDataFile(GameState->SkelJson, "data/spineboy-ess.json");
        GameState->MySkeleton = spSkeleton_create(GameState->SkelData);
        GameState->AnimationStateData = spAnimationStateData_create(GameState->SkelData);
        GameState->AnimationState = spAnimationState_create(GameState->AnimationStateData);

        spAnimationState_setAnimationByName(GameState->AnimationState, 0, "walk", 1);
        spAnimationState_addAnimationByName(GameState->AnimationState, 1, "run", 1, 1.0f);

        GameMemory->IsInitialized = true;
        ViewportWidth = 1280.0f;
        ViewportHeight = 720.0f;
    }

    spAnimationState_update(GameState->AnimationState, .007f);
    spAnimationState_apply(GameState->AnimationState, GameState->MySkeleton);

rest of code........
}

Edited by Jason on Reason: Initial post
where does the vtable field point to? and where is the function it points to located?

If they are in the reloaded code then you will have issues.
Ya it seems there are some function pointers the library sets to some of its own functions, in this case the 'apply' function of _spTimelineVtable (for which the VTABLE macro casts spTimeline to).

1
2
3
4
5
6
typedef struct _spTimelineVtable {
	void (*apply) (const spTimeline* self, spSkeleton* skeleton, float lastTime, float time, spEvent** firedEvents,
			int* eventsCount, float alpha, spMixBlend blend, spMixDirection direction);
	int (*getPropertyId) (const spTimeline* self);
	void (*dispose) (spTimeline* self);
} _spTimelineVtable;


Not sure how to fix this issue though. Is is possible to disable address randomization on msvc? Not sure if that would solve the issue but one thing I was thinking.

Edited by Jason on
You can disable address randomization with MSVC.

Set /DYNAMICBASE:NO - https://docs.microsoft.com/en-us/...ddress-space-layout-randomization
And set /FIXED to create fixed base address for DLL without relocations - https://docs.microsoft.com/en-us/...eference/fixed-fixed-base-address
This will require to use /BASE:somenumber to set base address - https://docs.microsoft.com/en-us/...build/reference/base-base-address

But it will not work. Because linker can place functions / global symbols in whatever order they want in executable. So next time DLL is loaded, sure it will be at same address, but functions may be at completely different addresses.

Instead figure out how your third-party library is setting up these function pointers. And reinitialize these values once you detect that reloading has happened. Either call some existing "init/setup" function or add extra code to reinitialize function pointers.

Edited by Mārtiņš Možeiko on
In case anyone else has an issue with live reloading and function pointers I ended up implementing what mmozeiko was suggesting. I would just find the appropriate function pointers and upon dll reloading I would set these pointers to the new function addresses. Fox anyone curious my specific fix looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
if(GlobalPlatformServices->ReloadedDLL || GameInput->InitialInputPlaybackAfterDLLReload)
    {
        GlobalPlatformServices->ReloadedDLL = false;
        GameInput->InitialInputPlaybackAfterDLLReload = false;

        for(i32 timelineIndex{0}; timelineIndex < GameState->AnimationState->tracks[0]->animation->timelinesCount; ++timelineIndex)
        {
            spTimeline *Timeline = GameState->AnimationState->tracks[0]->animation->timelines[timelineIndex];

            switch (Timeline->type)
            {
            case SP_TIMELINE_ROTATE:
            {
                VTABLE(spTimeline, Timeline)->apply = _spRotateTimeline_apply;
                VTABLE(spTimeline, Timeline)->getPropertyId = _spRotateTimeline_getPropertyId;
            }
            break;

            case SP_TIMELINE_SCALE:
            {
                VTABLE(spTimeline, Timeline)->apply = _spScaleTimeline_apply;
                VTABLE(spTimeline, Timeline)->getPropertyId = _spScaleTimeline_getPropertyId;
            }
            break;

            case SP_TIMELINE_SHEAR:
            {
                VTABLE(spTimeline, Timeline)->apply = _spShearTimeline_apply;
                VTABLE(spTimeline, Timeline)->getPropertyId = _spShearTimeline_getPropertyId;
            }
            break;

            case SP_TIMELINE_TRANSLATE:
            {
                VTABLE(spTimeline, Timeline)->apply = _spTranslateTimeline_apply;
                VTABLE(spTimeline, Timeline)->getPropertyId = _spTranslateTimeline_getPropertyId;
            }
            break;

            case SP_TIMELINE_EVENT:
            {
                VTABLE(spTimeline, Timeline)->apply = _spEventTimeline_apply;
                VTABLE(spTimeline, Timeline)->getPropertyId = _spEventTimeline_getPropertyId;
            }
            break;

            case SP_TIMELINE_ATTACHMENT:
            {
                VTABLE(spTimeline, Timeline)->apply = _spAttachmentTimeline_apply;
                VTABLE(spTimeline, Timeline)->getPropertyId = _spAttachmentTimeline_getPropertyId;
            }
            break;
            };
        };
    };


While this code is obviously specific to my project, just focus on the fact that I'm taking function addresses (_spAttachmentTimeline_appy, _spEventTimeline_apply, etc.) and just copying them to the current object's function pointer.

Notice that I also have a flag for telling if I'm currently reloading my dll while in inputplackback mode. This is also something to keep in mind depending on how you are playing back your recorded input state for the looped live code editing. For me, I'm just continually copying the initial game_state of my game when I first started to record my input and setting that as my game_state during each turn of my input plackback. This means, during inputplayback when trying to reload my dll again, my old function pointer addresses will be reset to before the dll reload occurred. In this case I need to make sure to reset the function pointers again to avoid a crash during input playback and dll reloading.
Awesome! Thanks for sharing the solution.