diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 89c42dc95..8cf69f974 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -576,7 +576,7 @@ namespace FlaxEditor.Viewport // Selected UI controls outline for (var i = 0; i < _window.Selection.Count; i++) { - if (_window.Selection[i].EditableObject is UIControl controlActor && controlActor.Control != null) + if (_window.Selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null) { var control = controlActor.Control; var bounds = Rectangle.FromPoints(control.PointToParent(this, Vector2.Zero), control.PointToParent(this, control.Size)); diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 2968b0645..5478cfbef 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -296,7 +296,7 @@ namespace FlaxEditor.Windows // Selected UI controls outline for (var i = 0; i < Editor.Instance.SceneEditing.Selection.Count; i++) { - if (Editor.Instance.SceneEditing.Selection[i].EditableObject is UIControl controlActor && controlActor.Control != null) + if (Editor.Instance.SceneEditing.Selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null) { var control = controlActor.Control; var bounds = Rectangle.FromPoints(control.PointToParent(_viewport, Vector2.Zero), control.PointToParent(_viewport, control.Size)); diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 049e9ebf3..8b8d4ae07 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -1037,9 +1037,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, bool autoI // Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it) // TODO: resave and force sync scenes during game cooking so this step could be skipped in game - Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping); SceneObjectsFactory::SynchronizePrefabInstances(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value); - Scripting::ObjectsLookupIdMapping.Set(nullptr); // Delete objects without parent for (int32 i = 1; i < objectsCount; i++) diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp index 512d6e9b7..77698e739 100644 --- a/Source/Engine/Level/Prefabs/PrefabManager.cpp +++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp @@ -16,6 +16,7 @@ #include "Engine/Core/Cache.h" #include "Engine/Debug/Exceptions/ArgumentException.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Scripting/Script.h" #include "Engine/Scripting/Scripting.h" #if USE_EDITOR @@ -216,21 +217,57 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, DictionaryIdsMapping); SceneObjectsFactory::SynchronizePrefabInstances(*sceneObjects.Value, actorToRemovedObjectsData, modifier.Value); - Scripting::ObjectsLookupIdMapping.Set(nullptr); } - // Delete objects without parent + // Delete objects without parent or with invalid linkage to the prefab for (int32 i = 1; i < sceneObjects->Count(); i++) { SceneObject* obj = sceneObjects->At(i); - if (obj && obj->GetParent() == nullptr) + if (!obj) + continue; + + // Check for missing parent (eg. parent object has been deleted) + if (obj->GetParent() == nullptr) { sceneObjects->At(i) = nullptr; LOG(Warning, "Scene object {0} {1} has missing parent object after load. Removing it.", obj->GetID(), obj->ToString()); obj->DeleteObject(); + continue; } + +#if USE_EDITOR && !BUILD_RELEASE + // Check for not being added to the parent (eg. invalid setup events fault on registration) + auto actor = dynamic_cast(obj); + auto script = dynamic_cast(obj); + if (obj->GetParent() == obj || (actor && !actor->GetParent()->Children.Contains(actor)) || (script && !script->GetParent()->Scripts.Contains(script))) + { + sceneObjects->At(i) = nullptr; + LOG(Warning, "Scene object {0} {1} has invalid parent object linkage after load. Removing it.", obj->GetID(), obj->ToString()); + obj->DeleteObject(); + continue; + } +#endif + +#if USE_EDITOR && BUILD_DEBUG + // Check for being added to parent not from spawned prefab (eg. invalid parentId linkage fault) + bool hasParentInInstance = false; + for (int32 j = 0; j < sceneObjects->Count(); j++) + { + if (sceneObjects->At(j) == obj->GetParent()) + { + hasParentInInstance = true; + break; + } + } + if (!hasParentInInstance) + { + sceneObjects->At(i) = nullptr; + LOG(Warning, "Scene object {0} {1} has invalid parent object after load. Removing it.", obj->GetID(), obj->ToString()); + obj->DeleteObject(); + continue; + } +#endif } // Link objects to prefab (only deserialized from prefab data) diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index a140b103c..e366a2f37 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -204,6 +204,8 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO { PROFILE_CPU_NAMED("SynchronizePrefabInstances"); + Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); + // Check all objects with prefab linkage for moving to a proper parent const int32 objectsToCheckCount = sceneObjects.Count(); for (int32 i = 0; i < objectsToCheckCount; i++) @@ -261,7 +263,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO // Preserve order in parent (values from prefab are used) if (i != 0) { - const auto defaultInstance = prefab ? prefab->GetDefaultInstance(obj->GetPrefabObjectID()) : nullptr; + const auto defaultInstance = prefab->GetDefaultInstance(obj->GetPrefabObjectID()); if (defaultInstance) { obj->SetOrderInParent(defaultInstance->GetOrderInParent()); @@ -315,6 +317,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO continue; // Create instance (including all children) + Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); SynchronizeNewPrefabInstance(prefab, actor, prefabObjectId, sceneObjects, modifier); } } @@ -325,6 +328,8 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Array& sceneO SceneObject* obj = sceneObjects[i]; obj->PostLoad(); } + + Scripting::ObjectsLookupIdMapping.Set(nullptr); } void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::DeserializeStream& value) @@ -424,6 +429,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Prefab* prefab, Actor* ac // Map prefab object ID to the new prefab object instance modifier->IdsMapping[prefabObjectId] = Guid::New(); + Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping); // Create prefab instance (recursive prefab loading to support nested prefabs) auto child = Spawn(*(ISerializable::DeserializeStream*)prefabData, modifier);