Fix nested prefab stack overflow when adding new object to nested prefabs hierarchy
https://github.com/LOOPDISK/FlaxEngine/pull/44
This commit is contained in:
@@ -820,8 +820,7 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD
|
||||
|
||||
void AnimatedModel::BeginPlay(SceneBeginData* data)
|
||||
{
|
||||
if (SkinnedModel && SkinnedModel->IsLoaded())
|
||||
PreInitSkinningData();
|
||||
PreInitSkinningData();
|
||||
|
||||
// Base
|
||||
ModelInstanceActor::BeginPlay(data);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Threading/MainThreadTask.h"
|
||||
#include "Editor/Editor.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
|
||||
// Apply flow:
|
||||
// - collect all prefabs using this prefab (load and create default instances)
|
||||
@@ -772,7 +773,13 @@ bool Prefab::ApplyAll(Actor* targetActor)
|
||||
if (ApplyAllInternal(targetActor, true, thisPrefabInstancesData))
|
||||
return true;
|
||||
|
||||
SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData);
|
||||
// Sync nested prefabs
|
||||
if (allPrefabs.HasItems())
|
||||
{
|
||||
LOG(Info, "Updating referencing prefabs");
|
||||
HashSet<Guid> synced;
|
||||
SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData, synced);
|
||||
}
|
||||
|
||||
const auto endTime = DateTime::NowUTC();
|
||||
LOG(Info, "Prefab updated! {0} ms", (int32)(endTime - startTime).GetTotalMilliseconds());
|
||||
@@ -1027,8 +1034,14 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
|
||||
rapidjson_flax::Document targetDataDocument;
|
||||
if (NestedPrefabs.HasItems())
|
||||
{
|
||||
// Use initial data buffer (unstripped) but reorder objects to match the sequence (eg. when new object was added to the nested prefab)
|
||||
targetDataDocument.Parse(dataBuffer.GetString(), dataBuffer.GetSize());
|
||||
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, targetDataDocument, modifier.Value);
|
||||
Array<SceneObject*> reorderedObjects = *sceneObjects.Value;
|
||||
newPrefabInstanceIdToDataIndexCounter = 0;
|
||||
for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i)
|
||||
reorderedObjects.Insert(i->Value, sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++));
|
||||
reorderedObjects.Resize(sceneObjects.Value->Count()); // reorderedObjects matches order in targetDataDocument
|
||||
SceneObjectsFactory::PrefabSyncData prefabSyncData(reorderedObjects, targetDataDocument, modifier.Value);
|
||||
SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData);
|
||||
|
||||
if (context.Instances.HasItems())
|
||||
@@ -1236,7 +1249,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
|
||||
{
|
||||
return Init(TypeName, StringAnsiView(tmpBuffer.GetString(), (int32)tmpBuffer.GetSize()));
|
||||
}
|
||||
#if 1 // Set to 0 to use memory-only reload that does not modifies the source file - useful for testing and debugging prefabs apply
|
||||
#if 1 // Set to 0 to use memory-only reload that does not modify the source file - useful for testing and debugging prefabs apply
|
||||
#if COMPILE_WITH_ASSETS_IMPORTER
|
||||
Locker.Unlock();
|
||||
|
||||
@@ -1295,7 +1308,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
|
||||
_defaultInstance->DeleteObject();
|
||||
_defaultInstance = nullptr;
|
||||
}
|
||||
_isLoaded = false;
|
||||
_loadState = 0;
|
||||
|
||||
// Update prefab data manually (to prevent updating source asset file - just for testing)
|
||||
Document.Parse(buffer.GetString(), buffer.GetSize());
|
||||
@@ -1348,7 +1361,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
|
||||
NestedPrefabs.Add(prefabId);
|
||||
}
|
||||
}
|
||||
_isLoaded = true;
|
||||
_loadState = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1395,34 +1408,31 @@ bool Prefab::SyncChangesInternal(PrefabInstancesData& prefabInstancesData)
|
||||
return ApplyAllInternal(targetActor, false, prefabInstancesData);
|
||||
}
|
||||
|
||||
void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData) const
|
||||
void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData, HashSet<Guid>& synced) const
|
||||
{
|
||||
PROFILE_CPU();
|
||||
LOG(Info, "Updating referencing prefabs");
|
||||
|
||||
// TODO: this may not work well for very complex prefab nesting -> loop order matters, maybe build a graph of dependencies?
|
||||
|
||||
// Call recursive for all referencing prefab assets to refresh nested prefabs
|
||||
for (int32 i = 0; i < allPrefabs.Count(); i++)
|
||||
{
|
||||
auto nestedPrefab = allPrefabs[i].Get();
|
||||
if (nestedPrefab)
|
||||
Prefab* nestedPrefab = allPrefabs[i].Get();
|
||||
if (!nestedPrefab || synced.Contains(nestedPrefab->GetID()))
|
||||
continue;
|
||||
if (nestedPrefab->WaitForLoaded())
|
||||
{
|
||||
if (nestedPrefab->WaitForLoaded())
|
||||
{
|
||||
LOG(Warning, "Waiting for prefab asset load failed.");
|
||||
continue;
|
||||
}
|
||||
LOG(Warning, "Waiting for '{}' load failed.", nestedPrefab->ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sync only if prefab is used by this prefab (directly) and it has been captured before
|
||||
const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID());
|
||||
if (nestedPrefabIndex != -1)
|
||||
{
|
||||
if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[i]))
|
||||
continue;
|
||||
nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData);
|
||||
ObjectsRemovalService::Flush();
|
||||
}
|
||||
// Sync only if prefab is used by this prefab (directly) and it has been captured before
|
||||
const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID());
|
||||
if (nestedPrefabIndex != -1)
|
||||
{
|
||||
synced.Add(nestedPrefab->GetID());
|
||||
if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[i]))
|
||||
continue;
|
||||
nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData, synced);
|
||||
ObjectsRemovalService::Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ private:
|
||||
bool ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData);
|
||||
bool UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, rapidjson_flax::StringBuffer& tmpBuffer);
|
||||
bool SyncChangesInternal(PrefabInstancesData& prefabInstancesData);
|
||||
void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData) const;
|
||||
void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData, HashSet<Guid, HeapAllocation>& synced) const;
|
||||
#endif
|
||||
void DeleteDefaultInstance();
|
||||
|
||||
|
||||
@@ -752,7 +752,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn
|
||||
obj->SetOrderInParent(order);
|
||||
}
|
||||
|
||||
// Setup hierarchy for the prefab instances (ensure any new objects are connected)
|
||||
// Setup hierarchy for the prefab instances (after adding new objects to ensure they are connected, eg. when reparenting existing prefab into a new root)
|
||||
for (const auto& instance : context.Instances)
|
||||
{
|
||||
const auto& prefabStartData = data.Data[instance.StatIndex];
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Engine/Level/Actors/EmptyActor.h"
|
||||
#include "Engine/Level/Actors/DirectionalLight.h"
|
||||
#include "Engine/Level/Actors/ExponentialHeightFog.h"
|
||||
#include "Engine/Level/Actors/AnimatedModel.h"
|
||||
#include "Engine/Level/Prefabs/Prefab.h"
|
||||
#include "Engine/Level/Prefabs/PrefabManager.h"
|
||||
#include "Engine/Scripting/ScriptingObjectReference.h"
|
||||
@@ -905,4 +906,124 @@ TEST_CASE("Prefabs")
|
||||
instance1->DeleteObject();
|
||||
instance2->DeleteObject();
|
||||
}
|
||||
SECTION("Test Adding Object To Base Prefab")
|
||||
{
|
||||
// https://github.com/LOOPDISK/FlaxEngine/pull/44
|
||||
|
||||
// Create inner prefab with 3 objects in hierarchy
|
||||
AssetReference<Prefab> prefabInner = Content::CreateVirtualAsset<Prefab>();
|
||||
REQUIRE(prefabInner);
|
||||
Guid id;
|
||||
Guid::Parse("15dbe4b0416be0777a6ce59e8788b10f", id);
|
||||
prefabInner->ChangeID(id);
|
||||
auto prefabInnerInit = prefabInner->Init(Prefab::TypeName,
|
||||
"["
|
||||
"{"
|
||||
"\"ID\": \"3de462104f56f681c14650a0171f88fb\","
|
||||
"\"TypeName\" : \"FlaxEngine.SpotLight\","
|
||||
"\"Name\" : \"Inner.Root\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"19b181f846b6911635ffacb902c93c6a\","
|
||||
"\"TypeName\" : \"FlaxEngine.StaticModel\","
|
||||
"\"ParentID\" : \"3de462104f56f681c14650a0171f88fb\","
|
||||
"\"Name\" : \"Inner.Cube\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"8950889f4a2e752d55165fbf10eaf184\","
|
||||
"\"TypeName\" : \"FlaxEngine.AnimatedModel\","
|
||||
"\"ParentID\" : \"19b181f846b6911635ffacb902c93c6a\","
|
||||
"\"Name\" : \"Inner.Model\""
|
||||
"}"
|
||||
"]");
|
||||
REQUIRE(!prefabInnerInit);
|
||||
|
||||
// Create outer prefab with 2 instances of inner prefab
|
||||
AssetReference<Prefab> prefabOuter = Content::CreateVirtualAsset<Prefab>();
|
||||
REQUIRE(prefabOuter);
|
||||
SCOPE_EXIT{ Content::DeleteAsset(prefabOuter); };
|
||||
Guid::Parse("2ab744714f746e31855f41815612d14b", id);
|
||||
prefabOuter->ChangeID(id);
|
||||
auto prefabOuterInit = prefabOuter->Init(Prefab::TypeName,
|
||||
"["
|
||||
"{"
|
||||
"\"ID\": \"dba7f4bb4acfd62608b9a8bf550f31a5\","
|
||||
"\"TypeName\": \"FlaxEngine.EmptyActor\","
|
||||
"\"Name\": \"Outer.Root\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"a3b705284432bed9f043829c04a2bc8f\","
|
||||
"\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\","
|
||||
"\"PrefabObjectID\": \"3de462104f56f681c14650a0171f88fb\","
|
||||
"\"ParentID\": \"dba7f4bb4acfd62608b9a8bf550f31a5\","
|
||||
"\"Name\": \"Instance 1\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"06a8c15a41b822dd27f3ac9d79b142d3\","
|
||||
"\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\","
|
||||
"\"PrefabObjectID\": \"19b181f846b6911635ffacb902c93c6a\","
|
||||
"\"ParentID\": \"a3b705284432bed9f043829c04a2bc8f\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"4759fb9e4c4dda3b61ab5ab43949e42f\","
|
||||
"\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\","
|
||||
"\"PrefabObjectID\": \"8950889f4a2e752d55165fbf10eaf184\","
|
||||
"\"ParentID\": \"06a8c15a41b822dd27f3ac9d79b142d3\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"1225be664c0c081e714bbf93e09b99e4\","
|
||||
"\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\","
|
||||
"\"PrefabObjectID\": \"3de462104f56f681c14650a0171f88fb\","
|
||||
"\"ParentID\": \"dba7f4bb4acfd62608b9a8bf550f31a5\","
|
||||
"\"Name\": \"Instance 2\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"b397243540322182b806ad8339b7b617\","
|
||||
"\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\","
|
||||
"\"PrefabObjectID\": \"19b181f846b6911635ffacb902c93c6a\","
|
||||
"\"ParentID\": \"1225be664c0c081e714bbf93e09b99e4\""
|
||||
"},"
|
||||
"{"
|
||||
"\"ID\": \"2c3b8e824daf038a58df528a238ca2de\","
|
||||
"\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\","
|
||||
"\"PrefabObjectID\": \"8950889f4a2e752d55165fbf10eaf184\","
|
||||
"\"ParentID\": \"b397243540322182b806ad8339b7b617\""
|
||||
"}"
|
||||
"]");
|
||||
REQUIRE(!prefabOuterInit);
|
||||
|
||||
// Spawn test instances of both prefabs
|
||||
ScriptingObjectReference<Actor> instanceInner = PrefabManager::SpawnPrefab(prefabInner);
|
||||
ScriptingObjectReference<Actor> instanceOuter = PrefabManager::SpawnPrefab(prefabOuter);
|
||||
|
||||
// Add new object to the inner prefab
|
||||
instanceInner->Children[0]->GetOrAddChild<DirectionalLight>();
|
||||
|
||||
// Apply changes
|
||||
bool applyResult = PrefabManager::ApplyAll(instanceInner);
|
||||
REQUIRE(!applyResult);
|
||||
|
||||
// Check state of outer instance to properly reflect hierarchy
|
||||
REQUIRE(instanceOuter);
|
||||
REQUIRE(instanceOuter->Children.Count() == 2);
|
||||
REQUIRE(instanceOuter->Children[0] != nullptr);
|
||||
REQUIRE(instanceOuter->Children[0]->Children.Count() == 1);
|
||||
REQUIRE(instanceOuter->Children[0]->Children[0]);
|
||||
REQUIRE(instanceOuter->Children[0]->Children[0]->Children.Count() == 2);
|
||||
REQUIRE(instanceOuter->Children[0]->Children[0]->Children[0]->Is<AnimatedModel>());
|
||||
REQUIRE(instanceOuter->Children[0]->Children[0]->Children[1]->Is<DirectionalLight>());
|
||||
REQUIRE(instanceOuter->Children[1] != nullptr);
|
||||
REQUIRE(instanceOuter->Children[1]->Children.Count() == 1);
|
||||
REQUIRE(instanceOuter->Children[1]->Children[0]);
|
||||
REQUIRE(instanceOuter->Children[1]->Children[0]->Children.Count() == 2);
|
||||
REQUIRE(instanceOuter->Children[1]->Children[0]->Children[0]->Is<AnimatedModel>());
|
||||
REQUIRE(instanceOuter->Children[0]->Children[0]->Children[1]->Is<DirectionalLight>());
|
||||
REQUIRE(instanceOuter->Children[0]->Children[0] != instanceOuter->Children[1]->Children[0]);
|
||||
REQUIRE(instanceOuter->Children[0]->Children[0]->Children[0] != instanceOuter->Children[1]->Children[0]->Children[0]);
|
||||
REQUIRE(instanceOuter->Children[0]->Children[0]->Children[1] != instanceOuter->Children[1]->Children[0]->Children[1]);
|
||||
|
||||
// Cleanup
|
||||
instanceInner->DeleteObject();
|
||||
instanceOuter->DeleteObject();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user