[LYN-3481] EMotionFX crashes when reloading an Actor (#1184)

* [LYN-3481] Added new actor instance request and notification buses

* [LYN-3481] Actor instance notifies bus about it being created or destroyed

* [LYN-3481] Selection lists are now automatically removing destroyed actor instances

* [LYN-3481] Morph targets window plugin reinitializing when used actor instance got destroyed

* [LYN-3481] Removing the OnDeleteActorInstance() from the emfx event handler/manager and porting the recorder to the new actor instance bus

* [LYN-3481] Removed the create actor instance calls from the event handler/manager

* [LYN-3481] Fixing automated tests
This commit is contained in:
Benjamin Jillich
2021-06-09 19:25:05 +02:00
committed by GitHub
parent fc33a0496d
commit eac0e42dc6
12 changed files with 107 additions and 79 deletions
@@ -20,10 +20,12 @@ namespace CommandSystem
SelectionList::SelectionList()
{
EMotionFX::ActorNotificationBus::Handler::BusConnect();
EMotionFX::ActorInstanceNotificationBus::Handler::BusConnect();
}
SelectionList::~SelectionList()
{
EMotionFX::ActorInstanceNotificationBus::Handler::BusDisconnect();
EMotionFX::ActorNotificationBus::Handler::BusDisconnect();
}
@@ -378,4 +380,9 @@ namespace CommandSystem
RemoveActor(actor);
}
void SelectionList::OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance)
{
RemoveActorInstance(actorInstance);
}
} // namespace CommandSystem
@@ -15,6 +15,7 @@
#include "CommandSystemConfig.h"
#include <EMotionFX/Source/ActorBus.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <EMotionFX/Source/ActorInstanceBus.h>
#include <EMotionFX/Source/Motion.h>
#include <EMotionFX/Source/Node.h>
#include <EMotionFX/Source/MotionInstance.h>
@@ -27,7 +28,8 @@ namespace CommandSystem
* specific time stamp in a scene.
*/
class COMMANDSYSTEM_API SelectionList
: EMotionFX::ActorNotificationBus::Handler
: private EMotionFX::ActorNotificationBus::Handler
, private EMotionFX::ActorInstanceNotificationBus::Handler
{
MCORE_MEMORYOBJECTCATEGORY(SelectionList, MCore::MCORE_DEFAULT_ALIGNMENT, MEMCATEGORY_COMMANDSYSTEM);
@@ -400,6 +402,9 @@ namespace CommandSystem
// ActorNotificationBus overrides
void OnActorDestroyed(EMotionFX::Actor* actor) override;
// ActorInstanceNotificationBus overrides
void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override;
AZStd::vector<EMotionFX::Node*> mSelectedNodes; /**< Array of selected nodes. */
AZStd::vector<EMotionFX::Actor*> mSelectedActors; /**< The selected actors. */
AZStd::vector<EMotionFX::ActorInstance*> mSelectedActorInstances; /**< Array of selected actor instances. */
@@ -34,6 +34,7 @@
#include "NodeGroup.h"
#include "Recorder.h"
#include "TransformData.h"
#include <EMotionFX/Source/ActorInstanceBus.h>
#include <EMotionFX/Source/DebugDraw.h>
#include <EMotionFX/Source/RagdollInstance.h>
@@ -153,20 +154,14 @@ namespace EMotionFX
// register it
GetActorManager().RegisterActorInstance(this);
// automatically register the actor instance
GetEventManager().OnCreateActorInstance(this);
GetActorManager().GetScheduler()->RecursiveInsertActorInstance(this);
ActorInstanceNotificationBus::Broadcast(&ActorInstanceNotificationBus::Events::OnActorInstanceCreated, this);
}
// the destructor
ActorInstance::~ActorInstance()
{
// trigger the OnDeleteActorInstance event
GetEventManager().OnDeleteActorInstance(this);
// remove it from the recording
GetRecorder().RemoveActorInstanceFromRecording(this);
ActorInstanceNotificationBus::Broadcast(&ActorInstanceNotificationBus::Events::OnActorInstanceDestroyed, this);
// get rid of the motion system
if (mMotionSystem)
@@ -0,0 +1,54 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
namespace EMotionFX
{
class ActorInstance;
/**
* EMotion FX Actor Instance Request Bus
* Used for making requests to actor instances.
*/
class ActorInstanceRequests
: public AZ::EBusTraits
{
public:
};
using ActorInstanceRequestBus = AZ::EBus<ActorInstanceRequests>;
/**
* EMotion FX Actor Instance Notification Bus
* Used for monitoring events from actor instances.
*/
class ActorInstanceNotifications
: public AZ::EBusTraits
{
public:
// Enable multi-threaded access by locking primitive using a mutex when connecting handlers to the EBus or executing events.
using MutexType = AZStd::recursive_mutex;
virtual void OnActorInstanceCreated([[maybe_unused]] ActorInstance* actorInstance) {}
/**
* Called when any of the actor instances gets destructed.
* @param actorInstance The actorInstance that gets destructed.
*/
virtual void OnActorInstanceDestroyed([[maybe_unused]] ActorInstance* actorInstance) {}
};
using ActorInstanceNotificationBus = AZ::EBus<ActorInstanceNotifications>;
} // namespace EMotionFX
@@ -51,7 +51,6 @@ namespace EMotionFX
EVENT_TYPE_MOTION_INSTANCE_LAST_EVENT = EVENT_TYPE_ON_QUEUE_MOTION_INSTANCE,
EVENT_TYPE_ON_DELETE_ACTOR,
EVENT_TYPE_ON_DELETE_ACTOR_INSTANCE,
EVENT_TYPE_ON_SIMULATE_PHYSICS,
EVENT_TYPE_ON_CUSTOM_EVENT,
EVENT_TYPE_ON_DRAW_LINE,
@@ -64,7 +63,6 @@ namespace EMotionFX
EVENT_TYPE_ON_CREATE_MOTION_INSTANCE,
EVENT_TYPE_ON_CREATE_MOTION_SYSTEM,
EVENT_TYPE_ON_CREATE_ACTOR,
EVENT_TYPE_ON_CREATE_ACTOR_INSTANCE,
EVENT_TYPE_ON_POST_CREATE_ACTOR,
EVENT_TYPE_ON_DELETE_ANIM_GRAPH,
EVENT_TYPE_ON_DELETE_ANIM_GRAPH_INSTANCE,
@@ -298,15 +296,6 @@ namespace EMotionFX
*/
virtual void OnDeleteActor(Actor* actor) { MCORE_UNUSED(actor); }
/**
* The event that gets triggered once an ActorInstance object is being deleted.
* You could for example use this event to delete any allocations you have done inside the
* custom user data object linked with the ActorInstance object.
* You can get and set this data object with the ActorInstance::GetCustomData() and ActorInstance::SetCustomData(...) methods.
* @param actorInstance The actorInstance that is being deleted.
*/
virtual void OnDeleteActorInstance(ActorInstance* actorInstance) { MCORE_UNUSED(actorInstance); }
virtual void OnSimulatePhysics(float timeDelta) { MCORE_UNUSED(timeDelta); }
virtual void OnCustomEvent(uint32 eventType, void* data) { MCORE_UNUSED(eventType); MCORE_UNUSED(data); }
@@ -321,7 +310,6 @@ namespace EMotionFX
virtual void OnCreateMotionInstance(MotionInstance* motionInstance) { MCORE_UNUSED(motionInstance); }
virtual void OnCreateMotionSystem(MotionSystem* motionSystem) { MCORE_UNUSED(motionSystem); }
virtual void OnCreateActor(Actor* actor) { MCORE_UNUSED(actor); }
virtual void OnCreateActorInstance(ActorInstance* actorInstance) { MCORE_UNUSED(actorInstance); }
virtual void OnPostCreateActor(Actor* actor) { MCORE_UNUSED(actor); }
// delete callbacks
@@ -305,16 +305,6 @@ namespace EMotionFX
}
void EventManager::OnDeleteActorInstance(ActorInstance* actorInstance)
{
const EventHandlerVector& eventHandlers = m_eventHandlersByEventType[EVENT_TYPE_ON_DELETE_ACTOR_INSTANCE];
for (EventHandler* eventHandler : eventHandlers)
{
eventHandler->OnDeleteActorInstance(actorInstance);
}
}
// draw a debug triangle
void EventManager::OnDrawTriangle(const AZ::Vector3& posA, const AZ::Vector3& posB, const AZ::Vector3& posC, const AZ::Vector3& normalA, const AZ::Vector3& normalB, const AZ::Vector3& normalC, uint32 color)
{
@@ -670,17 +660,6 @@ namespace EMotionFX
}
// create an actor instance
void EventManager::OnCreateActorInstance(ActorInstance* actorInstance)
{
const EventHandlerVector& eventHandlers = m_eventHandlersByEventType[EVENT_TYPE_ON_CREATE_ACTOR_INSTANCE];
for (EventHandler* eventHandler : eventHandlers)
{
eventHandler->OnCreateActorInstance(actorInstance);
}
}
// on post create actor
void EventManager::OnPostCreateActor(Actor* actor)
{
@@ -286,15 +286,6 @@ namespace EMotionFX
*/
void OnDeleteActor(Actor* actor);
/**
* The event that gets triggered once an ActorInstance object is being deleted.
* You could for example use this event to delete any allocations you have done inside the
* custom user data object linked with the ActorInstance object.
* You can get and set this data object with the ActorInstance::GetCustomData() and ActorInstance::SetCustomData(...) methods.
* @param actorInstance The actorInstance that is being deleted.
*/
void OnDeleteActorInstance(ActorInstance* actorInstance);
void OnSimulatePhysics(float timeDelta);
void OnCustomEvent(uint32 eventType, void* data);
void OnDrawTriangle(const AZ::Vector3& posA, const AZ::Vector3& posB, const AZ::Vector3& posC, const AZ::Vector3& normalA, const AZ::Vector3& normalB, const AZ::Vector3& normalC, uint32 color);
@@ -343,7 +334,6 @@ namespace EMotionFX
void OnCreateMotionInstance(MotionInstance* motionInstance);
void OnCreateMotionSystem(MotionSystem* motionSystem);
void OnCreateActor(Actor* actor);
void OnCreateActorInstance(ActorInstance* actorInstance);
void OnPostCreateActor(Actor* actor);
// delete callbacks
@@ -106,16 +106,12 @@ namespace EMotionFX
mCurrentPlayTime = 0.0f;
mObjects.SetMemoryCategory(EMFX_MEMCATEGORY_RECORDER);
GetEMotionFX().GetEventManager()->AddEventHandler(this);
EMotionFX::ActorInstanceNotificationBus::Handler::BusConnect();
}
Recorder::~Recorder()
{
if (EventManager* eventManager = GetEMotionFX().GetEventManager())
{
eventManager->RemoveEventHandler(this);
}
EMotionFX::ActorInstanceNotificationBus::Handler::BusDisconnect();
Clear();
}
@@ -1448,7 +1444,7 @@ namespace EMotionFX
Unlock();
}
void Recorder::OnDeleteActorInstance(ActorInstance* actorInstance)
void Recorder::OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance)
{
// Actor instances created by actor components do not use the command system and don't call a ClearRecorder command.
// Thus, these actor instances will have to be removed from the recorder to avoid dangling data.
@@ -23,6 +23,7 @@
#include <MCore/Source/File.h>
#include <MCore/Source/Vector.h>
#include <MCore/Source/MultiThreadManager.h>
#include <EMotionFX/Source/ActorInstanceBus.h>
#include <EMotionFX/Source/AnimGraphObjectIds.h>
#include <EMotionFX/Source/EventHandler.h>
#include <EMotionFX/Source/EventInfo.h>
@@ -47,7 +48,7 @@ namespace EMotionFX
class EMFX_API Recorder
: public BaseObject
, public EventHandler
, private EMotionFX::ActorInstanceNotificationBus::Handler
{
public:
AZ_CLASS_ALLOCATOR_DECL
@@ -319,9 +320,8 @@ namespace EMotionFX
void RemoveActorInstanceFromRecording(ActorInstance* actorInstance);
void RemoveAnimGraphFromRecording(AnimGraph* animGraph);
// EventHandler overrides
const AZStd::vector<EventTypes> GetHandledEventTypes() const override { return {EMotionFX::EVENT_TYPE_ON_DELETE_ACTOR_INSTANCE}; }
void OnDeleteActorInstance(ActorInstance* actorInstance) override;
// ActorInstanceNotificationBus overrides
void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override;
void SampleAndApplyTransforms(float timeInSeconds, ActorInstance* actorInstance) const;
void SampleAndApplyMainTransform(float timeInSeconds, ActorInstance* actorInstance) const;
@@ -17,25 +17,26 @@
#include "../../../../EMStudioSDK/Source/EMStudioCore.h"
#include <MCore/Source/LogManager.h>
#include <EMotionFX/CommandSystem/Source/CommandManager.h>
#include <EMotionFX/Source/ActorManager.h>
#include <EMotionFX/Source/MorphSetup.h>
#include "../../../../EMStudioSDK/Source/EMStudioManager.h"
namespace EMStudio
{
// constructor
MorphTargetsWindowPlugin::MorphTargetsWindowPlugin()
: EMStudio::DockWidgetPlugin()
{
mDialogStack = nullptr;
mCurrentActorInstance = nullptr;
mDialogStack = nullptr;
mCurrentActorInstance = nullptr;
mStaticTextWidget = nullptr;
EMotionFX::ActorInstanceNotificationBus::Handler::BusConnect();
}
// destructor
MorphTargetsWindowPlugin::~MorphTargetsWindowPlugin()
{
EMotionFX::ActorInstanceNotificationBus::Handler::BusDisconnect();
// unregister the command callbacks and get rid of the memory
for (auto callback : m_callbacks)
{
@@ -110,14 +111,16 @@ namespace EMStudio
mMorphTargetGroups.clear();
}
// reinit the morph target dialog, e.g. if selection changes
void MorphTargetsWindowPlugin::ReInit(bool forceReInit)
{
// get the selected actorinstance
const CommandSystem::SelectionList& selection = GetCommandManager()->GetCurrentSelection();
EMotionFX::ActorInstance* actorInstance = selection.GetSingleActorInstance();
const CommandSystem::SelectionList& selection = GetCommandManager()->GetCurrentSelection();
EMotionFX::ActorInstance* actorInstance = selection.GetSingleActorInstance();
ReInit(actorInstance, forceReInit);
}
void MorphTargetsWindowPlugin::ReInit(EMotionFX::ActorInstance* actorInstance, bool forceReInit)
{
// show hint if no/multiple actor instances is/are selected
if (actorInstance == nullptr)
{
@@ -135,10 +138,7 @@ namespace EMStudio
return;
}
// get our selected actor instance and the corresponding actor
EMotionFX::Actor* actor = actorInstance->GetActor();
// only reinit the morph targets if actorinstance changed
// only reinit the morph targets if actor instance changed
if (mCurrentActorInstance != actorInstance || forceReInit)
{
// set the current actor instance in any case
@@ -150,7 +150,7 @@ namespace EMStudio
AZStd::vector<EMotionFX::MorphSetupInstance::MorphTarget*> phonemeInstances;
AZStd::vector<EMotionFX::MorphSetupInstance::MorphTarget*> defaultMorphTargetInstances;
// get the morph target setup
EMotionFX::Actor* actor = actorInstance->GetActor();
EMotionFX::MorphSetup* morphSetup = actor->GetMorphSetup(actorInstance->GetLODLevel());
if (morphSetup == nullptr)
{
@@ -278,6 +278,13 @@ namespace EMStudio
}
}
void MorphTargetsWindowPlugin::OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance)
{
if (mCurrentActorInstance == actorInstance)
{
ReInit(/*actorInstance=*/nullptr);
}
}
//-----------------------------------------------------------------------------------------
// Command callbacks
@@ -16,6 +16,7 @@
#include <MysticQt/Source/DialogStack.h>
#include "../../../../EMStudioSDK/Source/DockWidgetPlugin.h"
#include <EMotionFX/CommandSystem/Source/SelectionCommands.h>
#include <EMotionFX/Source/ActorInstanceBus.h>
#include "MorphTargetGroupWidget.h"
#include <QVBoxLayout>
#include <QLabel>
@@ -26,6 +27,7 @@ namespace EMStudio
{
class MorphTargetsWindowPlugin
: public EMStudio::DockWidgetPlugin
, private EMotionFX::ActorInstanceNotificationBus::Handler
{
Q_OBJECT
MCORE_MEMORYOBJECTCATEGORY(MorphTargetsWindowPlugin, MCore::MCORE_DEFAULT_ALIGNMENT, MEMCATEGORY_STANDARDPLUGINS);
@@ -54,6 +56,7 @@ namespace EMStudio
EMStudioPlugin* Clone() override;
// update the morph targets window based on the current selection
void ReInit(EMotionFX::ActorInstance* actorInstance, bool forceReInit = false);
void ReInit(bool forceReInit = false);
// clear all widgets from the window
@@ -70,6 +73,9 @@ namespace EMStudio
void WindowReInit(bool visible);
private:
// ActorInstanceNotificationBus overrides
void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override;
// declare the callbacks
MCORE_DEFINECOMMANDCALLBACK(CommandSelectCallback);
MCORE_DEFINECOMMANDCALLBACK(CommandUnselectCallback);
@@ -15,6 +15,7 @@ set(FILES
Source/ActorBus.h
Source/ActorInstance.cpp
Source/ActorInstance.h
Source/ActorInstanceBus.h
Source/ActorManager.cpp
Source/ActorManager.h
Source/ActorUpdateScheduler.h