- Modifié
spine and game dll reloading exception
Hello,
I've been having an issue trying to consistently get my dynamic code reloading working with spine-c animations. Currently, I have a giant memory block that I allocate from my main exe and this memory is passed to my gamecode.dll file. The memory I allocate in my exe (with Windows virtualalloc()) is set to have a base address that I specify so my game code memory will always be in the same addresses. This way, I can have all my game data saved to this memory block, which is controlled by the exe, and just reload the dll file and have everything just work again upon reload. I have setup spine, through the extension.h file, to utilize my game memory and everything has worked fine up until I added some animations.
The issue I'm having is sometimes an exception is thrown on this line of spine upon building and trying to reload my gamecode dll:
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);
}
Again, sometimes my dll reloading works perfectly fine and sometimes is doesn't (about 50/50). I was wondering if anyone could help me work through what is happening to hopefully solve the issue. If you need to checkout my code then I can provide the github link. Here is what my current spine implementation looks like inside my gamecode dll:
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........
}
Thank you.
An animation is composed of timelines of various types, e.g. rotation timeline, scaling timeline etc. For each of these timeline types, an apply
method exists that is used by spAnimationState
code to update a skeleton. In runtimes not written in C, this is done by a simple class hierarchy. In the spine-c runtime, we emulate a class hierarchy (or interface) by a simple manual vtable, see spine-runtimes/Animation.h at 3.6
For each timeline, we set a function pointer in that vtable. My assumption is that when you reload your shared library code, the data survives properly, but the function addresses change. When that apply function of a timline is called by spAnimationState_xxx
through the timeline's vtable, the function's address is invalid and you get a crash.
I'd be surprised if Windows has a function similar to virtualalloc
that'd guarantee function addresses stay the same across reloads. I think your only option is to go through all the timelines in all the loaded spAnimation
instances, and update their respective vtable.
Thanks for the reply. Ya, after some more thought it was looking like I would have to update the timelines to their new function addresses upon dll reload. I was able to implement something like the following upon reload:
if(GlobalPlatformServices->HasDLLBeenReloaded)
{
GlobalPlatformServices->HasDLLBeenReloaded = 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;
//...etc
It seems to work a bit better now but still not perfect. Could be my own code but does this look like a decent way to go about this? I'm still relatively new to the library so still figuring out how all the moving parts are working.
This looks mostly correct. However, you want to iterate over all spAnimation
instances found in spSkeletonData
. Your code will only fix up timelines currently enqueued in an animation state on track 0. By fixing the spAnimation
instances in spSkeletonData
you are sure to catch everything.
Okay great. Thanks a bunch. And awesome job with this software and library, really good stuff.