Skip to content

Commit 4a3ee11

Browse files
authored
Fix several issues with importing actor assets (o3de#18056) (o3de#18119)
* Fix several issues with importing actor assets * Fixes a crash when viewing the import settings of a scene. The crash was caused by Qt objects interacting with non-qt threads. * Fixes a crash caused by inactive entities being constructed in a background thread during scene settings (ie, the prefab builder stuff) interacting with the real actual loaded entities and undo stack in the editor. * Fixes a crash caused by morph targets that are empty still making it into the data. * Fixes a crash caused by a mixture of skinned and unskinned meshes being combined in a skinned mesh asset by default generating skin info if they are part of a skin export but lack it. * Adds additional debug output for future debugging of these issues, this debug output is not enabled by default and uses the existing "debug output" checkbox in the Asset Processor GUI to enable it. When active, dumps a lot more detail from the scene and export into the log. Signed-off-by: Nicholas Lawson <[email protected]>
1 parent ca9c0b3 commit 4a3ee11

File tree

11 files changed

+334
-89
lines changed

11 files changed

+334
-89
lines changed

Code/Editor/Plugins/EditorAssetImporter/AssetImporterWindow.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,13 +255,14 @@ void AssetImporterWindow::OpenFileInternal(const AZStd::string& filePath)
255255
s_browseTag,
256256
[this, filePath]()
257257
{
258+
// this is invoked across threads, so ensure that nothing touches the main thread that isn't thread safe.
259+
// Qt objects, in particular, should talk via timers or queued connections.
258260
m_assetImporterDocument->LoadScene(filePath);
259-
260-
QTimer::singleShot(0, [&]() { UpdateSceneDisplay({}); });
261+
QMetaObject::invokeMethod(this, &AssetImporterWindow::UpdateDefaultSceneDisplay, Qt::QueuedConnection);
261262
},
262263
[this]()
263264
{
264-
QTimer::singleShot(0, [&]() { HandleAssetLoadingCompleted();});
265+
QMetaObject::invokeMethod(this, &AssetImporterWindow::HandleAssetLoadingCompleted, Qt::QueuedConnection);
265266
}, this);
266267

267268
QFileInfo fileInfo(filePath.c_str());
@@ -621,6 +622,11 @@ void AssetImporterWindow::OverlayLayerRemoved()
621622
}
622623
}
623624

625+
void AssetImporterWindow::UpdateDefaultSceneDisplay()
626+
{
627+
UpdateSceneDisplay({});
628+
}
629+
624630
void AssetImporterWindow::UpdateSceneDisplay(const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene> scene) const
625631
{
626632
QString sceneHeaderText;

Code/Editor/Plugins/EditorAssetImporter/AssetImporterWindow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ private slots:
119119

120120
void OverlayLayerAdded();
121121
void OverlayLayerRemoved();
122+
void UpdateDefaultSceneDisplay();
122123
void UpdateSceneDisplay(const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene> scene = {}) const;
123124

124125
void FileChanged(QString path);

Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorComponentBase.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,19 @@ namespace AzToolsFramework
130130

131131
void EditorComponentBase::SetDirty()
132132
{
133-
if (GetEntity())
133+
// Don't mark things for dirty that are not active.
134+
// Entities can be inactive for a number of reasons, for example, in a prefab file
135+
// being constructed on another thread, and while these might still invoke SetDirty
136+
// in response to properties changing, only actualized entities should interact with
137+
// the undo/redo system or the prefab change tracker for overrides, which is what
138+
// marking things dirty is for.
139+
if (AZ::Entity* entity = GetEntity();entity)
134140
{
135-
AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(
136-
&AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity, GetEntity()->GetId());
141+
if (entity->GetState() == AZ::Entity::State::Active)
142+
{
143+
AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity, entity->GetId());
144+
}
145+
137146
}
138147
else
139148
{

Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <AzCore/IO/SystemFile.h>
1313
#include <AzCore/Serialization/Json/JsonUtils.h>
1414
#include <AzCore/Serialization/Utils.h>
15+
#include <AzCore/Settings/SettingsRegistry.h>
1516
#include <AzFramework/StringFunc/StringFunc.h>
1617
#include <SceneAPI/SceneCore/Containers/Scene.h>
1718
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
@@ -22,6 +23,16 @@
2223

2324
namespace AZ::SceneAPI::Utilities
2425
{
26+
bool IsDebugEnabled()
27+
{
28+
bool resultValue = false;
29+
if (auto* registry = AZ::SettingsRegistry::Get())
30+
{
31+
registry->Get(resultValue, AZ::SceneAPI::Utilities::Key_AssetProcessorInDebugOutput);
32+
}
33+
return resultValue;
34+
}
35+
2536
bool SaveToJson(const AZStd::string& fileName, const DebugSceneGraph& graph);
2637

2738
void DebugNode::Reflect(AZ::ReflectContext* context)

Code/Tools/SceneAPI/SceneCore/Utilities/DebugOutput.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ namespace AZ::SceneAPI::Utilities
3737
{
3838
constexpr int SceneGraphVersion = 1;
3939

40+
//! IsDebugEnabled - returns true if additional debug output is desired from scene processing.
41+
SCENE_CORE_API bool IsDebugEnabled();
42+
4043
struct DebugNode
4144
{
4245
AZ_TYPE_INFO(DebugNode, "{490B9D4C-1847-46EB-BEBC-49812E104626}");

Code/Tools/SceneAPI/SceneUI/Handlers/ProcessingHandlers/AsyncOperationProcessingHandler.cpp

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include <AzToolsFramework/Debug/TraceContext.h>
1212
#include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/AsyncOperationProcessingHandler.h>
1313

14+
#include <QTimer>
15+
1416
namespace AZ
1517
{
1618
namespace SceneAPI
@@ -27,22 +29,26 @@ namespace AZ
2729
void AsyncOperationProcessingHandler::BeginProcessing()
2830
{
2931
emit StatusMessageUpdated("Waiting for background processes to complete...");
30-
m_thread.reset(
31-
new AZStd::thread(
32-
[this]()
33-
{
34-
AZ_TraceContext("Tag", m_traceTag);
35-
m_operationToRun();
36-
EBUS_QUEUE_FUNCTION(AZ::TickBus, AZStd::bind(&AsyncOperationProcessingHandler::OnBackgroundOperationComplete, this));
37-
}
38-
)
32+
// Note that the use of a QThread instead of an AZStd::thread is intentional here, as signals, slots, timers, and other parts
33+
// of Qt will cause weird behavior and crashes if invoked from a non-QThread. Qt tries its best to compensate, but without
34+
// a QThread as context, it may not correctly be able to invoke cross-thread event queues, or safely store objects in QThreadStorage.
35+
m_thread = QThread::create(
36+
[this]()
37+
{
38+
AZ_TraceContext("Tag", m_traceTag);
39+
m_operationToRun();
40+
QMetaObject::invokeMethod(this, &AsyncOperationProcessingHandler::OnBackgroundOperationComplete, Qt::QueuedConnection);
41+
}
3942
);
43+
m_thread->start();
4044
}
4145

4246
void AsyncOperationProcessingHandler::OnBackgroundOperationComplete()
4347
{
44-
m_thread->detach();
45-
m_thread.reset(nullptr);
48+
m_thread->quit(); // signal the thread's event pump to exit (at this point, its almost certainly already completed)
49+
m_thread->wait(); // wait for the thread to clean up any state, as well as actually join (ie, exit) so that it is no longer running.
50+
delete m_thread;
51+
m_thread = nullptr;
4652

4753
emit StatusMessageUpdated("Processing complete");
4854
if (m_onComplete)

Code/Tools/SceneAPI/SceneUI/Handlers/ProcessingHandlers/AsyncOperationProcessingHandler.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <AzCore/std/functional.h>
1414
#include <AzCore/std/smart_ptr/unique_ptr.h>
1515
#include <SceneAPI/SceneUI/SceneUIConfiguration.h>
16+
#include <QThread>
1617
#endif
1718

1819
namespace AZStd
@@ -34,14 +35,14 @@ namespace AZ
3435
~AsyncOperationProcessingHandler() override = default;
3536
void BeginProcessing() override;
3637

37-
private:
38+
private Q_SLOTS:
3839
void OnBackgroundOperationComplete();
3940

4041
private:
4142
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
4243
AZStd::function<void()> m_operationToRun;
4344
AZStd::function<void()> m_onComplete;
44-
AZStd::unique_ptr<AZStd::thread> m_thread;
45+
QThread* m_thread = nullptr;
4546
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
4647
};
4748
}

0 commit comments

Comments
 (0)