#include #include "MemoryTestFixture.h" #include "SpineEventMonitor.h" #include "spine/spine.h" #include "KMemory.h" // last include #define SPINEBOY_JSON "testdata/spineboy/spineboy-ess.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 != 0); spSkeletonData *skeletonData = spSkeletonJson_readSkeletonDataFile(json, filename); ASSERT(skeletonData != 0); 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 != 0); skeletonData = readSkeletonJsonData(SPINEBOY_JSON, atlas); ASSERT(skeletonData != 0); stateData = spAnimationStateData_create(skeletonData); ASSERT(stateData != 0); stateData->defaultMix = 0.4f; // force mixing /////////////////////////////////////////////////////////////////////////// // Animation Instance skeleton = spSkeleton_create(skeletonData); ASSERT(skeleton != 0); state = spAnimationState_create(stateData); ASSERT(state != 0); } 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 = 0; spSkeletonData *skeletonData = 0; spAnimationStateData *stateData = 0; spSkeleton *skeleton = 0; spAnimationState *state = 0; ////////////////////////////////////////////////////////////////////////// // 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 = 0; spSkeletonData *skeletonData = 0; spAnimationStateData *stateData = 0; spSkeleton *skeleton = 0; spAnimationState *state = 0; ////////////////////////////////////////////////////////////////////////// // 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 = 0; 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 = 0; spSkeletonData *skeletonData = 0; spAnimationStateData *stateData = 0; spAnimationState *state = 0; ////////////////////////////////////////////////////////////////////////// // 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); } void MemoryTestFixture::triangulator() { spTriangulator *triangulator = spTriangulator_create(); spFloatArray *polygon = spFloatArray_create(16); spFloatArray_add(polygon, 0); spFloatArray_add(polygon, 0); spFloatArray_add(polygon, 100); spFloatArray_add(polygon, 0); spFloatArray_add(polygon, 100); spFloatArray_add(polygon, 100); spFloatArray_add(polygon, 0); spFloatArray_add(polygon, 100); spShortArray *triangles = spTriangulator_triangulate(triangulator, polygon); ASSERT(triangles->size == 6); ASSERT(triangles->items[0] == 3); ASSERT(triangles->items[1] == 0); ASSERT(triangles->items[2] == 1); ASSERT(triangles->items[3] == 3); ASSERT(triangles->items[4] == 1); ASSERT(triangles->items[5] == 2); spArrayFloatArray *polys = spTriangulator_decompose(triangulator, polygon, triangles); ASSERT(polys->size == 1); ASSERT(polys->items[0]->size == 8); ASSERT(polys->items[0]->items[0] == 0); ASSERT(polys->items[0]->items[1] == 100); ASSERT(polys->items[0]->items[2] == 0); ASSERT(polys->items[0]->items[3] == 0); ASSERT(polys->items[0]->items[4] == 100); ASSERT(polys->items[0]->items[5] == 0); ASSERT(polys->items[0]->items[6] == 100); ASSERT(polys->items[0]->items[7] == 100); spFloatArray_dispose(polygon); spTriangulator_dispose(triangulator); } void MemoryTestFixture::skeletonClipper() { spSkeletonClipping *clipping = spSkeletonClipping_create(); spBoneData *boneData = spBoneData_create(0, "bone", 0); spBone *bone = spBone_create(boneData, 0, 0); CONST_CAST(float, bone->a) = 1; CONST_CAST(float, bone->b) = 0; CONST_CAST(float, bone->c) = 0; CONST_CAST(float, bone->d) = 1; CONST_CAST(float, bone->worldX) = 0; CONST_CAST(float, bone->worldY) = 0; spSlotData *slotData = spSlotData_create(0, "slot", 0); spSlot *slot = spSlot_create(slotData, bone); spClippingAttachment *clip = spClippingAttachment_create("clipping"); clip->endSlot = slotData; clip->super.worldVerticesLength = 4 * 2; clip->super.verticesCount = 4; clip->super.vertices = MALLOC(float, 4 * 8); clip->super.vertices[0] = 0; clip->super.vertices[1] = 50; clip->super.vertices[2] = 100; clip->super.vertices[3] = 50; clip->super.vertices[4] = 100; clip->super.vertices[5] = 70; clip->super.vertices[6] = 0; clip->super.vertices[7] = 70; spSkeletonClipping_clipStart(clipping, slot, clip); spFloatArray *vertices = spFloatArray_create(16); spFloatArray_add(vertices, 0); spFloatArray_add(vertices, 0); spFloatArray_add(vertices, 100); spFloatArray_add(vertices, 0); spFloatArray_add(vertices, 50); spFloatArray_add(vertices, 150); spFloatArray *uvs = spFloatArray_create(16); spFloatArray_add(uvs, 0); spFloatArray_add(uvs, 0); spFloatArray_add(uvs, 1); spFloatArray_add(uvs, 0); spFloatArray_add(uvs, 0.5f); spFloatArray_add(uvs, 1); spUnsignedShortArray *indices = spUnsignedShortArray_create(16); spUnsignedShortArray_add(indices, 0); spUnsignedShortArray_add(indices, 1); spUnsignedShortArray_add(indices, 2); spSkeletonClipping_clipTriangles(clipping, vertices->items, vertices->size, indices->items, indices->size, uvs->items, 2); float expectedVertices[8] = {83.333328f, 50.000000f, 76.666664f, 70.000000f, 23.333334f, 70.000000f, 16.666672f, 50.000000f}; ASSERT(clipping->clippedVertices->size == 8); for (int i = 0; i < clipping->clippedVertices->size; i++) { ASSERT(ABS(clipping->clippedVertices->items[i] - expectedVertices[i]) < 0.001); } float expectedUVs[8] = {0.833333f, 0.333333f, 0.766667f, 0.466667f, 0.233333f, 0.466667f, 0.166667f, 0.333333f}; ASSERT(clipping->clippedUVs->size == 8); for (int i = 0; i < clipping->clippedUVs->size; i++) { ASSERT(ABS(clipping->clippedUVs->items[i] - expectedUVs[i]) < 0.001); } short expectedIndices[6] = {0, 1, 2, 0, 2, 3}; ASSERT(clipping->clippedTriangles->size == 6); for (int i = 0; i < clipping->clippedTriangles->size; i++) { ASSERT(clipping->clippedTriangles->items[i] == expectedIndices[i]); } spFloatArray_dispose(vertices); spFloatArray_dispose(uvs); spUnsignedShortArray_dispose(indices); spSlotData_dispose(slotData); spSlot_dispose(slot); spBoneData_dispose(boneData); spBone_dispose(bone); _spClippingAttachment_dispose(SUPER(SUPER(clip))); spSkeletonClipping_dispose(clipping); }