223 lines
7.2 KiB
C++
Executable File

#include "MemoryTestFixture.h"
#include "SpineEventMonitor.h"
#include "spine/spine.h"
#include "KMemory.h" // last include
#define SPINEBOY_JSON "testdata/spineboy/spineboy.json"
#define SPINEBOY_ATLAS "testdata/spineboy/spineboy.atlas"
#define MAX_RUN_TIME 6000 // equal to about 100 seconds of execution
MemoryTestFixture::~MemoryTestFixture()
{
finalize();
}
void MemoryTestFixture::initialize()
{
// on a Per- Fixture Basis, before Test execution
}
void MemoryTestFixture::finalize()
{
// on a Per- Fixture Basis, after all tests pass/fail
}
void MemoryTestFixture::setUp()
{
// Setup on Per-Test Basis
}
void MemoryTestFixture::tearDown()
{
// Tear Down on Per-Test Basis
}
//////////////////////////////////////////////////////////////////////////
// Helper methods
static spSkeletonData* readSkeletonJsonData(const char* filename, spAtlas* atlas) {
spSkeletonJson* json = spSkeletonJson_create(atlas);
ASSERT(json != nullptr);
spSkeletonData* skeletonData = spSkeletonJson_readSkeletonDataFile(json, filename);
ASSERT(skeletonData != nullptr);
spSkeletonJson_dispose(json);
return skeletonData;
}
static void LoadSpineboyExample(spAtlas* &atlas, spSkeletonData* &skeletonData, spAnimationStateData* &stateData, spSkeleton* &skeleton, spAnimationState* &state)
{
///////////////////////////////////////////////////////////////////////////
// Global Animation Information
atlas = spAtlas_createFromFile(SPINEBOY_ATLAS, 0);
ASSERT(atlas != nullptr);
skeletonData = readSkeletonJsonData(SPINEBOY_JSON, atlas);
ASSERT(skeletonData != nullptr);
stateData = spAnimationStateData_create(skeletonData);
ASSERT(stateData != nullptr);
stateData->defaultMix = 0.4f; // force mixing
///////////////////////////////////////////////////////////////////////////
// Animation Instance
skeleton = spSkeleton_create(skeletonData);
ASSERT(skeleton != nullptr);
state = spAnimationState_create(stateData);
ASSERT(state != nullptr);
}
static void DisposeAll(spSkeleton* skeleton, spAnimationState* state, spAnimationStateData* stateData, spSkeletonData* skeletonData, spAtlas* atlas)
{
///////////////////////////////////////////////////////////////////////////
// Dispose Instance
spSkeleton_dispose(skeleton);
spAnimationState_dispose(state);
///////////////////////////////////////////////////////////////////////////
// Dispose Global
spAnimationStateData_dispose(stateData);
spSkeletonData_dispose(skeletonData);
spAtlas_dispose(atlas);
}
//////////////////////////////////////////////////////////////////////////
// Reproduce Memory leak as described in Issue #776
// https://github.com/EsotericSoftware/spine-runtimes/issues/776
void MemoryTestFixture::reproduceIssue_776()
{
spAtlas* atlas = nullptr;
spSkeletonData* skeletonData = nullptr;
spAnimationStateData* stateData = nullptr;
spSkeleton* skeleton = nullptr;
spAnimationState* state = nullptr;
//////////////////////////////////////////////////////////////////////////
// Initialize Animations
LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
///////////////////////////////////////////////////////////////////////////
// Run animation
spSkeleton_setToSetupPose(skeleton);
InterruptMonitor eventMonitor(state);
//eventMonitor.SetDebugLogging(true);
// Interrupt the animation on this specific sequence of spEventType(s)
eventMonitor
.AddInterruptEvent(SP_ANIMATION_INTERRUPT, "jump")
.AddInterruptEvent(SP_ANIMATION_START);
spAnimationState_setAnimationByName(state, 0, "walk", true);
spAnimationState_addAnimationByName(state, 0, "jump", false, 0.0f);
spAnimationState_addAnimationByName(state, 0, "run", true, 0.0f);
spAnimationState_addAnimationByName(state, 0, "jump", false, 3.0f);
spAnimationState_addAnimationByName(state, 0, "walk", true, 0.0f);
spAnimationState_addAnimationByName(state, 0, "idle", false, 1.0f);
for (int i = 0; i < MAX_RUN_TIME && eventMonitor.isAnimationPlaying(); ++i) {
const float timeSlice = 1.0f / 60.0f;
spSkeleton_update(skeleton, timeSlice);
spAnimationState_update(state, timeSlice);
spAnimationState_apply(state, skeleton);
}
//////////////////////////////////////////////////////////////////////////
// Cleanup Animations
DisposeAll(skeleton, state, stateData, skeletonData, atlas);
}
void MemoryTestFixture::reproduceIssue_777()
{
spAtlas* atlas = nullptr;
spSkeletonData* skeletonData = nullptr;
spAnimationStateData* stateData = nullptr;
spSkeleton* skeleton = nullptr;
spAnimationState* state = nullptr;
//////////////////////////////////////////////////////////////////////////
// Initialize Animations
LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
///////////////////////////////////////////////////////////////////////////
// Run animation
spSkeleton_setToSetupPose(skeleton);
SpineEventMonitor eventMonitor(state);
//eventMonitor.SetDebugLogging(true);
// Set Animation and Play for 5 frames
spAnimationState_setAnimationByName(state, 0, "walk", true);
for (int i = 0; i < 5; ++i) {
const float timeSlice = 1.0f / 60.0f;
spSkeleton_update(skeleton, timeSlice);
spAnimationState_update(state, timeSlice);
spAnimationState_apply(state, skeleton);
}
// Change animation twice in a row
spAnimationState_setAnimationByName(state, 0, "walk", false);
spAnimationState_setAnimationByName(state, 0, "run", false);
// run normal update
for (int i = 0; i < 5; ++i) {
const float timeSlice = 1.0f / 60.0f;
spSkeleton_update(skeleton, timeSlice);
spAnimationState_update(state, timeSlice);
spAnimationState_apply(state, skeleton);
}
// Now we'd lose mixingFrom (the first "walk" entry we set above) and should leak
spAnimationState_setAnimationByName(state, 0, "run", false);
//////////////////////////////////////////////////////////////////////////
// Cleanup Animations
DisposeAll(skeleton, state, stateData, skeletonData, atlas);
}
spSkeleton* skeleton = nullptr;
static void spineAnimStateHandler(spAnimationState* state, int type, spTrackEntry* entry, spEvent* event)
{
if (type == SP_ANIMATION_COMPLETE)
{
spAnimationState_setAnimationByName(state, 0, "walk", false);
spAnimationState_update(state, 0);
spAnimationState_apply(state, skeleton);
}
}
void MemoryTestFixture::reproduceIssue_Loop()
{
spAtlas* atlas = nullptr;
spSkeletonData* skeletonData = nullptr;
spAnimationStateData* stateData = nullptr;
spAnimationState* state = nullptr;
//////////////////////////////////////////////////////////////////////////
// Initialize Animations
LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);
///////////////////////////////////////////////////////////////////////////
if (state)
state->listener = (spAnimationStateListener)&spineAnimStateHandler;
spAnimationState_setAnimationByName(state, 0, "walk", false);
// run normal update
for (int i = 0; i < 50; ++i) {
const float timeSlice = 1.0f / 60.0f;
spSkeleton_update(skeleton, timeSlice);
spAnimationState_update(state, timeSlice);
spAnimationState_apply(state, skeleton);
}
DisposeAll(skeleton, state, stateData, skeletonData, atlas);
}