From 425bb8411df1ad7aa377ad2a6df197213779f887 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Sun, 26 Apr 2026 17:56:19 +0300 Subject: [PATCH] Fixed crash with entity destruction --- .../editScene/systems/AnimationTreeSystem.cpp | 430 +++++++++--------- .../editScene/systems/AnimationTreeSystem.hpp | 25 +- 2 files changed, 238 insertions(+), 217 deletions(-) diff --git a/src/features/editScene/systems/AnimationTreeSystem.cpp b/src/features/editScene/systems/AnimationTreeSystem.cpp index fe510d5..d311c25 100644 --- a/src/features/editScene/systems/AnimationTreeSystem.cpp +++ b/src/features/editScene/systems/AnimationTreeSystem.cpp @@ -61,7 +61,8 @@ Ogre::Entity *AnimationTreeSystem::findAnimatedEntity(flecs::entity e) t.node->getAttachedObject(i); if (obj->getMovableType() == "Entity") { Ogre::Entity *ent = - static_cast(obj); + static_cast( + obj); if (ent->hasSkeleton()) return ent; } @@ -87,13 +88,14 @@ bool AnimationTreeSystem::setupEntity(flecs::entity e, EntityAnimTreeState &state = m_states[e.id()]; state.ogreEntity = ent; + state.ogreEntityName = ent->getName(); state.rootBone = nullptr; state.animations.clear(); Ogre::SkeletonInstance *skel = ent->getSkeleton(); /* Find and freeze root bone */ - const char *rootNames[] = {"Root", "mixamorig:Hips", "Spineroot"}; + const char *rootNames[] = { "Root", "mixamorig:Hips", "Spineroot" }; for (const char *name : rootNames) { try { state.rootBone = skel->getBone(name); @@ -102,7 +104,8 @@ bool AnimationTreeSystem::setupEntity(flecs::entity e, } } if (state.rootBone) { - state.rootBindingPosition = state.rootBone->getInitialPosition(); + state.rootBindingPosition = + state.rootBone->getInitialPosition(); state.rootBindingOrientation = state.rootBone->getInitialOrientation(); state.rootBindingScale = state.rootBone->getInitialScale(); @@ -124,11 +127,9 @@ bool AnimationTreeSystem::setupEntity(flecs::entity e, * Animation::apply() in OGRE 14) */ if (state.rootBone) { as->destroyBlendMask(); - as->createBlendMask(skel->getNumBones(), - 1.0f); + as->createBlendMask(skel->getNumBones(), 1.0f); as->setBlendMaskEntry( - state.rootBone->getHandle(), - 0.0f); + state.rootBone->getHandle(), 0.0f); } AnimationRuntimeInfo info; @@ -142,18 +143,20 @@ bool AnimationTreeSystem::setupEntity(flecs::entity e, state.rootBone->getHandle(); if (anim->hasNodeTrack(handle)) { info.rootTrack = - anim->getNodeTrack(handle); - if (info.rootTrack->getNumKeyFrames() >= + anim->getNodeTrack( + handle); + if (info.rootTrack + ->getNumKeyFrames() >= 2) { Ogre::TransformKeyFrame *tkfBeg = - info.rootTrack - ->getNodeKeyFrame(0); - Ogre::TransformKeyFrame *tkfEnd = info.rootTrack ->getNodeKeyFrame( - info.rootTrack - ->getNumKeyFrames() - - 1); + 0); + Ogre::TransformKeyFrame *tkfEnd = + info.rootTrack->getNodeKeyFrame( + info.rootTrack + ->getNumKeyFrames() - + 1); info.loopTranslation = tkfEnd->getTranslate() - tkfBeg->getTranslate(); @@ -169,9 +172,11 @@ bool AnimationTreeSystem::setupEntity(flecs::entity e, /* Initialize default state machine states */ initializeTreeStates(at.root, at); - std::cout << " setupEntity done: animations=" << state.animations.size() - << " rootBone=" << (state.rootBone ? state.rootBone->getName() : "null") - << std::endl; + std::cout + << " setupEntity done: animations=" << state.animations.size() + << " rootBone=" + << (state.rootBone ? state.rootBone->getName() : "null") + << std::endl; return true; } @@ -183,16 +188,40 @@ void AnimationTreeSystem::teardownEntity(flecs::entity e) EntityAnimTreeState &state = it->second; - for (auto &pair : state.animations) { - if (pair.second.ogreAnimState) - pair.second.ogreAnimState->destroyBlendMask(); + /* + * The Ogre::Entity may have already been destroyed by + * EditorUISystem::deleteEntity before the Flecs OnRemove + * observer fires. In that case the AnimationState pointers + * are dangling and we must not touch them. + * + * We detect this by checking whether the cached Ogre::Entity + * name is still known to the scene manager. We use the + * cached name string (ogreEntityName) rather than + * ogreEntity->getName() because the pointer may already be + * dangling. + */ + bool ogreEntityAlive = false; + if (!state.ogreEntityName.empty()) { + try { + ogreEntityAlive = + m_sceneMgr->hasEntity(state.ogreEntityName); + } catch (...) { + ogreEntityAlive = false; + } } - disableAllAnimations(state); + if (ogreEntityAlive) { + for (auto &pair : state.animations) { + if (pair.second.ogreAnimState) + pair.second.ogreAnimState->destroyBlendMask(); + } - if (state.rootBone) { - state.rootBone->setManuallyControlled(false); - state.rootBone = nullptr; + disableAllAnimations(state); + + if (state.rootBone) { + state.rootBone->setManuallyControlled(false); + state.rootBone = nullptr; + } } state.animations.clear(); @@ -210,15 +239,16 @@ void AnimationTreeSystem::disableAllAnimations(EntityAnimTreeState &state) } } -void AnimationTreeSystem::initializeTreeStates( - const AnimationTreeNode &node, AnimationTreeComponent &at) +void AnimationTreeSystem::initializeTreeStates(const AnimationTreeNode &node, + AnimationTreeComponent &at) { if (node.type == "stateMachine") { if (at.currentStates.find(node.name) == at.currentStates.end()) { for (const auto &child : node.children) { if (child.type == "state") { - at.currentStates[node.name] = child.name; + at.currentStates[node.name] = + child.name; break; } } @@ -236,9 +266,9 @@ void AnimationTreeSystem::resolveTemplate(AnimationTreeComponent &at) AnimationTreeTemplate *templ = nullptr; AnimationTreeComponent *templAt = nullptr; - m_world.query() - .each([&](flecs::entity, AnimationTreeTemplate &t, - AnimationTreeComponent &ta) { + m_world.query().each( + [&](flecs::entity, AnimationTreeTemplate &t, + AnimationTreeComponent &ta) { if (t.name == at.templateName) { templ = &t; templAt = &ta; @@ -246,8 +276,8 @@ void AnimationTreeSystem::resolveTemplate(AnimationTreeComponent &at) }); if (!templ) { - m_world.query() - .each([&](flecs::entity, AnimationTreeTemplate &t) { + m_world.query().each( + [&](flecs::entity, AnimationTreeTemplate &t) { if (t.name == at.templateName) templ = &t; }); @@ -266,159 +296,155 @@ void AnimationTreeSystem::update(float deltaTime) if (!m_initialized) return; - m_world.query().each( - [this, deltaTime](flecs::entity e, - AnimationTreeComponent &at) { - resolveTemplate(at); + m_world.query().each([this, deltaTime]( + flecs::entity e, + AnimationTreeComponent + &at) { + resolveTemplate(at); - if (at.dirty) { - if (setupEntity(e, at)) - at.dirty = false; - } + if (at.dirty) { + if (setupEntity(e, at)) + at.dirty = false; + } - auto it = m_states.find(e.id()); - if (it == m_states.end()) - return; + auto it = m_states.find(e.id()); + if (it == m_states.end()) + return; - /* Validate cached entity pointer - + /* Validate cached entity pointer - * CharacterSlotSystem rebuilds can destroy and * recreate the Ogre::Entity */ - Ogre::Entity *currentEnt = findAnimatedEntity(e); - if (currentEnt != it->second.ogreEntity) { - if (!setupEntity(e, at)) { - return; - } - it = m_states.find(e.id()); - if (it == m_states.end()) - return; - } - - EntityAnimTreeState &state = it->second; - if (!state.ogreEntity) - return; - - if (!at.enabled) { - disableAllAnimations(state); + Ogre::Entity *currentEnt = findAnimatedEntity(e); + if (currentEnt != it->second.ogreEntity) { + if (!setupEntity(e, at)) { return; } + it = m_states.find(e.id()); + if (it == m_states.end()) + return; + } - /* Sync external state changes (editor) */ - syncStateChanges(at, state); + EntityAnimTreeState &state = it->second; + if (!state.ogreEntity) + return; - /* Evaluate tree */ - EvalContext ctx; - ctx.deltaTime = deltaTime; - evaluateNode(at.root, 1.0f, 1.0f, at, state, ctx); + if (!at.enabled) { + disableAllAnimations(state); + return; + } - /* Apply animation weights and advance */ - Ogre::Vector3 totalRootMotion = - Ogre::Vector3::ZERO; - Ogre::SceneNode *sceneNode = - state.ogreEntity->getParentSceneNode(); + /* Sync external state changes (editor) */ + syncStateChanges(at, state); - for (auto &pair : ctx.animData) { - const Ogre::String &animName = pair.first; - AnimEvalData &data = pair.second; + /* Evaluate tree */ + EvalContext ctx; + ctx.deltaTime = deltaTime; + evaluateNode(at.root, 1.0f, 1.0f, at, state, ctx); - auto itAnim = - state.animations.find(animName); - if (itAnim == state.animations.end()) - continue; - AnimationRuntimeInfo &info = - itAnim->second; + /* Apply animation weights and advance */ + Ogre::Vector3 totalRootMotion = Ogre::Vector3::ZERO; + Ogre::SceneNode *sceneNode = + state.ogreEntity->getParentSceneNode(); - if (!info.ogreAnimState) - continue; + for (auto &pair : ctx.animData) { + const Ogre::String &animName = pair.first; + AnimEvalData &data = pair.second; - bool active = data.weight > 0.001f; - info.ogreAnimState->setEnabled(active); - info.ogreAnimState->setWeight( - Ogre::Math::Clamp(data.weight, 0.0f, - 1.0f)); + auto itAnim = state.animations.find(animName); + if (itAnim == state.animations.end()) + continue; + AnimationRuntimeInfo &info = itAnim->second; - if (active && data.timeDelta != 0.0f) { - float lastTime = info.ogreAnimState - ->getTimePosition(); - info.ogreAnimState->addTime( - data.timeDelta); - float thisTime = - info.ogreAnimState - ->getTimePosition(); - float length = - info.ogreAnimState - ->getLength(); - bool loop = - info.ogreAnimState - ->getLoop(); + if (!info.ogreAnimState) + continue; - int loops = 0; - if (loop && length > 0.0f) { - loops = (int)std::round( - (lastTime + data.timeDelta - - thisTime) / - length); - } + bool active = data.weight > 0.001f; + info.ogreAnimState->setEnabled(active); + info.ogreAnimState->setWeight( + Ogre::Math::Clamp(data.weight, 0.0f, 1.0f)); - if (at.useRootMotion && - info.rootTrack) { - Ogre::TransformKeyFrame tkf( - nullptr, 0.0f); - info.rootTrack - ->getInterpolatedKeyFrame( - lastTime, &tkf); - Ogre::Vector3 lastPos = - tkf.getTranslate(); - info.rootTrack - ->getInterpolatedKeyFrame( - thisTime, &tkf); - Ogre::Vector3 thisPos = - tkf.getTranslate(); - Ogre::Vector3 delta = - thisPos - lastPos + - loops * - info.loopTranslation; - totalRootMotion += - delta * data.weight; - } + if (active && data.timeDelta != 0.0f) { + float lastTime = + info.ogreAnimState->getTimePosition(); + info.ogreAnimState->addTime(data.timeDelta); + float thisTime = + info.ogreAnimState->getTimePosition(); + float length = info.ogreAnimState->getLength(); + bool loop = info.ogreAnimState->getLoop(); + + int loops = 0; + if (loop && length > 0.0f) { + loops = (int)std::round( + (lastTime + data.timeDelta - + thisTime) / + length); + } + + if (at.useRootMotion && info.rootTrack) { + Ogre::TransformKeyFrame tkf(nullptr, + 0.0f); + info.rootTrack->getInterpolatedKeyFrame( + lastTime, &tkf); + Ogre::Vector3 lastPos = + tkf.getTranslate(); + info.rootTrack->getInterpolatedKeyFrame( + thisTime, &tkf); + Ogre::Vector3 thisPos = + tkf.getTranslate(); + Ogre::Vector3 delta = + thisPos - lastPos + + loops * info.loopTranslation; + totalRootMotion += delta * data.weight; } } + } - if (at.useRootMotion && sceneNode) { - if (e.has()) { - auto &cc = e.get_mut(); - if (deltaTime > 0.0000001f) { - float safeDelta = Ogre::Math::Clamp(deltaTime, 0.005f, 0.99f); - Ogre::Quaternion worldRot = sceneNode->_getDerivedOrientation(); - cc.linearVelocity = worldRot * totalRootMotion / safeDelta; - cc.linearVelocity.x = Ogre::Math::Clamp(cc.linearVelocity.x, -16.0f, 16.0f); - cc.linearVelocity.z = Ogre::Math::Clamp(cc.linearVelocity.z, -16.0f, 16.0f); - cc.linearVelocity.y = Ogre::Math::Clamp(cc.linearVelocity.y, -10.5f, 10.0f); - } - } else { - sceneNode->translate(totalRootMotion, + if (at.useRootMotion && sceneNode) { + if (e.has()) { + auto &cc = e.get_mut(); + if (deltaTime > 0.0000001f) { + float safeDelta = Ogre::Math::Clamp( + deltaTime, 0.005f, 0.99f); + Ogre::Quaternion worldRot = + sceneNode + ->_getDerivedOrientation(); + cc.linearVelocity = worldRot * + totalRootMotion / + safeDelta; + cc.linearVelocity.x = Ogre::Math::Clamp( + cc.linearVelocity.x, -16.0f, + 16.0f); + cc.linearVelocity.z = Ogre::Math::Clamp( + cc.linearVelocity.z, -16.0f, + 16.0f); + cc.linearVelocity.y = Ogre::Math::Clamp( + cc.linearVelocity.y, -10.5f, + 10.0f); + } + } else { + sceneNode->translate(totalRootMotion, Ogre::Node::TS_LOCAL); - } } + } - /* Reset root bone to binding pose */ - if (state.rootBone) { - state.rootBone->setPosition( - state.rootBindingPosition); - state.rootBone->setOrientation( - state.rootBindingOrientation); - state.rootBone->setScale( - state.rootBindingScale); - } + /* Reset root bone to binding pose */ + if (state.rootBone) { + state.rootBone->setPosition(state.rootBindingPosition); + state.rootBone->setOrientation( + state.rootBindingOrientation); + state.rootBone->setScale(state.rootBindingScale); + } - /* Handle end-of-animation transitions */ - checkEndTransitions(e, at, state, ctx); - }); + /* Handle end-of-animation transitions */ + checkEndTransitions(e, at, state, ctx); + }); } -void AnimationTreeSystem::evaluateNode( - const AnimationTreeNode &node, float parentWeight, float timeMul, - const AnimationTreeComponent &at, EntityAnimTreeState &state, - EvalContext &ctx) +void AnimationTreeSystem::evaluateNode(const AnimationTreeNode &node, + float parentWeight, float timeMul, + const AnimationTreeComponent &at, + EntityAnimTreeState &state, + EvalContext &ctx) { if (parentWeight < 0.001f) return; @@ -453,8 +479,7 @@ void AnimationTreeSystem::evaluateNode( /* Update fades */ std::vector doneIn; for (const auto &inName : fade.fadeIn) { - fade.weights[inName] += - ctx.deltaTime * node.fadeSpeed; + fade.weights[inName] += ctx.deltaTime * node.fadeSpeed; if (fade.weights[inName] >= 1.0f) { fade.weights[inName] = 1.0f; doneIn.push_back(inName); @@ -465,8 +490,7 @@ void AnimationTreeSystem::evaluateNode( std::vector doneOut; for (const auto &outName : fade.fadeOut) { - fade.weights[outName] -= - ctx.deltaTime * node.fadeSpeed; + fade.weights[outName] -= ctx.deltaTime * node.fadeSpeed; if (fade.weights[outName] <= 0.0f) { fade.weights[outName] = 0.0f; doneOut.push_back(outName); @@ -481,19 +505,18 @@ void AnimationTreeSystem::evaluateNode( continue; float w = fade.weights[child.name]; if (w > 0.001f) - evaluateNode(child, parentWeight * w, - timeMul, at, state, ctx); + evaluateNode(child, parentWeight * w, timeMul, + at, state, ctx); } /* Queue end-transition check */ if (node.endTransitions.find(currentName) != node.endTransitions.end()) - ctx.endChecks.push_back( - {node.name, currentName}); + ctx.endChecks.push_back({ node.name, currentName }); } else if (node.type == "state") { if (!node.children.empty()) - evaluateNode(node.children[0], parentWeight, - timeMul, at, state, ctx); + evaluateNode(node.children[0], parentWeight, timeMul, + at, state, ctx); } else if (node.type == "animation") { auto &data = ctx.animData[node.animationName]; data.weight += parentWeight; @@ -501,8 +524,8 @@ void AnimationTreeSystem::evaluateNode( } } -void AnimationTreeSystem::syncStateChanges( - AnimationTreeComponent &at, EntityAnimTreeState &state) +void AnimationTreeSystem::syncStateChanges(AnimationTreeComponent &at, + EntityAnimTreeState &state) { for (auto &pair : at.currentStates) { auto itPrev = state.prevStates.find(pair.first); @@ -519,15 +542,15 @@ void AnimationTreeSystem::syncStateChanges( } /* Also track newly added state machines */ for (auto &pair : at.currentStates) { - if (state.prevStates.find(pair.first) == - state.prevStates.end()) + if (state.prevStates.find(pair.first) == state.prevStates.end()) state.prevStates[pair.first] = pair.second; } } -void AnimationTreeSystem::checkEndTransitions( - flecs::entity e, AnimationTreeComponent &at, - EntityAnimTreeState &state, EvalContext &ctx) +void AnimationTreeSystem::checkEndTransitions(flecs::entity e, + AnimationTreeComponent &at, + EntityAnimTreeState &state, + EvalContext &ctx) { for (auto &pair : ctx.endChecks) { const Ogre::String &smName = pair.first; @@ -546,32 +569,28 @@ void AnimationTreeSystem::checkEndTransitions( findStateNode(*smNode, stateName); if (!stNode) continue; - const AnimationTreeNode *animNode = - findAnimationNode(*stNode); + const AnimationTreeNode *animNode = findAnimationNode(*stNode); if (!animNode) continue; - auto itAnim = state.animations.find( - animNode->animationName); + auto itAnim = state.animations.find(animNode->animationName); if (itAnim == state.animations.end()) continue; - Ogre::AnimationState *as = - itAnim->second.ogreAnimState; + Ogre::AnimationState *as = itAnim->second.ogreAnimState; if (!as) continue; float t = as->getTimePosition(); float len = as->getLength(); if (len > 0.0f && t >= len * 0.99f) - setStateInternal(e, at, state, smName, - itTrans->second, true); + setStateInternal(e, at, state, smName, itTrans->second, + true); } } void AnimationTreeSystem::setState(flecs::entity e, const Ogre::String &stateMachineName, - const Ogre::String &stateName, - bool reset) + const Ogre::String &stateName, bool reset) { auto it = m_states.find(e.id()); if (it == m_states.end()) @@ -579,15 +598,15 @@ void AnimationTreeSystem::setState(flecs::entity e, if (!e.has()) return; auto &at = e.get_mut(); - setStateInternal(e, at, it->second, stateMachineName, - stateName, reset); + setStateInternal(e, at, it->second, stateMachineName, stateName, reset); } -void AnimationTreeSystem::setStateInternal( - flecs::entity e, AnimationTreeComponent &at, - EntityAnimTreeState &state, - const Ogre::String &stateMachineName, - const Ogre::String &stateName, bool reset) +void AnimationTreeSystem::setStateInternal(flecs::entity e, + AnimationTreeComponent &at, + EntityAnimTreeState &state, + const Ogre::String &stateMachineName, + const Ogre::String &stateName, + bool reset) { (void)e; auto itCurrent = at.currentStates.find(stateMachineName); @@ -627,8 +646,9 @@ void AnimationTreeSystem::setStateInternal( } } -Ogre::String AnimationTreeSystem::getCurrentState( - flecs::entity e, const Ogre::String &stateMachineName) +Ogre::String +AnimationTreeSystem::getCurrentState(flecs::entity e, + const Ogre::String &stateMachineName) { if (!e.has()) return ""; @@ -639,8 +659,9 @@ Ogre::String AnimationTreeSystem::getCurrentState( return ""; } -const AnimationTreeNode *AnimationTreeSystem::findStateMachineNode( - const AnimationTreeNode &root, const Ogre::String &name) const +const AnimationTreeNode * +AnimationTreeSystem::findStateMachineNode(const AnimationTreeNode &root, + const Ogre::String &name) const { if (root.type == "stateMachine" && root.name == name) return &root; @@ -653,9 +674,9 @@ const AnimationTreeNode *AnimationTreeSystem::findStateMachineNode( return nullptr; } -const AnimationTreeNode *AnimationTreeSystem::findStateNode( - const AnimationTreeNode &smNode, - const Ogre::String &stateName) const +const AnimationTreeNode * +AnimationTreeSystem::findStateNode(const AnimationTreeNode &smNode, + const Ogre::String &stateName) const { for (const auto &child : smNode.children) { if (child.type == "state" && child.name == stateName) @@ -664,14 +685,13 @@ const AnimationTreeNode *AnimationTreeSystem::findStateNode( return nullptr; } -const AnimationTreeNode *AnimationTreeSystem::findAnimationNode( - const AnimationTreeNode &stateNode) const +const AnimationTreeNode * +AnimationTreeSystem::findAnimationNode(const AnimationTreeNode &stateNode) const { if (stateNode.type == "animation") return &stateNode; for (const auto &child : stateNode.children) { - const AnimationTreeNode *found = - findAnimationNode(child); + const AnimationTreeNode *found = findAnimationNode(child); if (found) return found; } diff --git a/src/features/editScene/systems/AnimationTreeSystem.hpp b/src/features/editScene/systems/AnimationTreeSystem.hpp index 00024b1..264ff7f 100644 --- a/src/features/editScene/systems/AnimationTreeSystem.hpp +++ b/src/features/editScene/systems/AnimationTreeSystem.hpp @@ -61,12 +61,14 @@ private: struct EntityAnimTreeState { Ogre::Entity *ogreEntity = nullptr; + Ogre::String ogreEntityName; /* cached for safe teardown */ Ogre::Bone *rootBone = nullptr; Ogre::Vector3 rootBindingPosition; Ogre::Quaternion rootBindingOrientation; Ogre::Vector3 rootBindingScale; - std::unordered_map animations; + std::unordered_map + animations; std::unordered_map fadeStates; /* Track previous currentStates to detect external changes */ std::unordered_map prevStates; @@ -80,7 +82,7 @@ private: struct EvalContext { float deltaTime = 0.0f; std::unordered_map animData; - std::vector> endChecks; + std::vector > endChecks; }; bool setupEntity(flecs::entity e, AnimationTreeComponent &at); @@ -92,8 +94,7 @@ private: float timeMul, const AnimationTreeComponent &at, EntityAnimTreeState &state, EvalContext &ctx); void checkEndTransitions(flecs::entity e, AnimationTreeComponent &at, - EntityAnimTreeState &state, - EvalContext &ctx); + EntityAnimTreeState &state, EvalContext &ctx); void syncStateChanges(AnimationTreeComponent &at, EntityAnimTreeState &state); void setStateInternal(flecs::entity e, AnimationTreeComponent &at, @@ -104,14 +105,14 @@ private: /* Resolve template reference and copy tree if template changed */ void resolveTemplate(AnimationTreeComponent &at); - const AnimationTreeNode *findStateMachineNode( - const AnimationTreeNode &root, - const Ogre::String &name) const; - const AnimationTreeNode *findStateNode( - const AnimationTreeNode &smNode, - const Ogre::String &stateName) const; - const AnimationTreeNode *findAnimationNode( - const AnimationTreeNode &stateNode) const; + const AnimationTreeNode * + findStateMachineNode(const AnimationTreeNode &root, + const Ogre::String &name) const; + const AnimationTreeNode * + findStateNode(const AnimationTreeNode &smNode, + const Ogre::String &stateName) const; + const AnimationTreeNode * + findAnimationNode(const AnimationTreeNode &stateNode) const; flecs::world &m_world; Ogre::SceneManager *m_sceneMgr;