Add SpawnOptions container for more robust prefabs spawning

This commit is contained in:
Wojtek Figat
2025-01-20 23:50:47 +01:00
parent 86444aa5f3
commit 18fd68db25
3 changed files with 113 additions and 50 deletions

View File

@@ -181,6 +181,18 @@ public:
obj->SetParent(nullptr); obj->SetParent(nullptr);
obj->DeleteObject(); obj->DeleteObject();
} }
static void SerializeObjects(const ActorsCache::SceneObjectsListType& sceneObjects, JsonWriter& writer)
{
PROFILE_CPU();
writer.StartArray();
for (int32 i = 0; i < sceneObjects.Count(); i++)
{
SceneObject* obj = sceneObjects[i];
writer.SceneObject(obj);
}
writer.EndArray();
}
}; };
void PrefabInstanceData::CollectPrefabInstances(PrefabInstancesData& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor) void PrefabInstanceData::CollectPrefabInstances(PrefabInstancesData& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor)
@@ -235,14 +247,7 @@ void PrefabInstanceData::SerializePrefabInstances(PrefabInstancesData& prefabIns
// Serialize // Serialize
tmpBuffer.Clear(); tmpBuffer.Clear();
CompactJsonWriter writerObj(tmpBuffer); CompactJsonWriter writerObj(tmpBuffer);
JsonWriter& writer = writerObj; SerializeObjects(*sceneObjects.Value, writerObj);
writer.StartArray();
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects.Value->At(i);
writer.SceneObject(obj);
}
writer.EndArray();
// Parse json to get DOM // Parse json to get DOM
{ {
@@ -746,14 +751,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
rapidjson_flax::StringBuffer dataBuffer; rapidjson_flax::StringBuffer dataBuffer;
{ {
CompactJsonWriter writerObj(dataBuffer); CompactJsonWriter writerObj(dataBuffer);
JsonWriter& writer = writerObj; PrefabInstanceData::SerializeObjects(*targetObjects.Value, writerObj);
writer.StartArray();
for (int32 i = 0; i < targetObjects->Count(); i++)
{
SceneObject* obj = targetObjects.Value->At(i);
writer.SceneObject(obj);
}
writer.EndArray();
} }
// Parse json document and modify serialized data to extract only modified properties // Parse json document and modify serialized data to extract only modified properties
@@ -1079,14 +1077,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
{ {
tmpBuffer.Clear(); tmpBuffer.Clear();
PrettyJsonWriter writerObj(tmpBuffer); PrettyJsonWriter writerObj(tmpBuffer);
JsonWriter& writer = writerObj; PrefabInstanceData::SerializeObjects(defaultInstanceObjects, writerObj);
writer.StartArray();
for (int32 i = 0; i < defaultInstanceObjects.Count(); i++)
{
auto obj = defaultInstanceObjects.At(i);
writer.SceneObject(obj);
}
writer.EndArray();
} }
LOG(Info, "Updating prefab data"); LOG(Info, "Updating prefab data");

View File

@@ -40,7 +40,9 @@ PrefabManagerService PrefabManagerServiceInstance;
Actor* PrefabManager::SpawnPrefab(Prefab* prefab) Actor* PrefabManager::SpawnPrefab(Prefab* prefab)
{ {
Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr; Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr;
return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr); SpawnOptions options;
options.Parent = parent;
return SpawnPrefab(prefab, options);
} }
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position) Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position)
@@ -69,20 +71,39 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform)
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, const Transform& transform) Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, const Transform& transform)
{ {
return SpawnPrefab(prefab, transform, parent, nullptr); SpawnOptions options;
options.Transform = &transform;
options.Parent = parent;
return SpawnPrefab(prefab, options);
} }
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent) Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent)
{ {
return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr); SpawnOptions options;
options.Parent = parent;
return SpawnPrefab(prefab, options);
} }
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, SceneObject*>* objectsCache, bool withSynchronization) Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, SceneObject*>* objectsCache, bool withSynchronization)
{ {
return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, objectsCache, withSynchronization); SpawnOptions options;
options.Parent = parent;
options.ObjectsCache = objectsCache;
options.WithSync = withSynchronization;
return SpawnPrefab(prefab, options);
} }
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, SceneObject*>* objectsCache, bool withSynchronization) Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, SceneObject*>* objectsCache, bool withSynchronization)
{
SpawnOptions options;
options.Transform = &transform;
options.Parent = parent;
options.ObjectsCache = objectsCache;
options.WithSync = withSynchronization;
return SpawnPrefab(prefab, options);
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const SpawnOptions& options)
{ {
PROFILE_CPU_NAMED("Prefab.Spawn"); PROFILE_CPU_NAMED("Prefab.Spawn");
if (prefab == nullptr) if (prefab == nullptr)
@@ -111,15 +132,27 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
sceneObjects->Resize(dataCount); sceneObjects->Resize(dataCount);
CollectionPoolCache<ISerializeModifier, Cache::ISerializeModifierClearCallback>::ScopeCache modifier = Cache::ISerializeModifier.Get(); CollectionPoolCache<ISerializeModifier, Cache::ISerializeModifierClearCallback>::ScopeCache modifier = Cache::ISerializeModifier.Get();
modifier->EngineBuild = prefab->DataEngineBuild; modifier->EngineBuild = prefab->DataEngineBuild;
for (int32 i = 0; i < prefab->ObjectsIds.Count(); i++)
modifier->IdsMapping.EnsureCapacity(prefab->ObjectsIds.Count()); modifier->IdsMapping.EnsureCapacity(prefab->ObjectsIds.Count());
if (options.IDs)
{ {
modifier->IdsMapping.Add(prefab->ObjectsIds[i], Guid::New()); for (int32 i = 0; i < prefab->ObjectsIds.Count(); i++)
{
Guid prefabObjectId = prefab->ObjectsIds[i];
Guid id;
if (!options.IDs->TryGet(prefabObjectId, id))
id = Guid::New();
modifier->IdsMapping.Add(id, id);
}
} }
if (objectsCache) else
{ {
objectsCache->Clear(); for (int32 i = 0; i < prefab->ObjectsIds.Count(); i++)
objectsCache->SetCapacity(prefab->ObjectsDataCache.Capacity()); modifier->IdsMapping.Add(prefab->ObjectsIds[i], Guid::New());
}
if (options.ObjectsCache)
{
options.ObjectsCache->Clear();
options.ObjectsCache->SetCapacity(prefab->ObjectsDataCache.Count());
} }
auto& data = *prefab->Data; auto& data = *prefab->Data;
SceneObjectsFactory::Context context(modifier.Value); SceneObjectsFactory::Context context(modifier.Value);
@@ -139,7 +172,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
SceneObjectsFactory::HandleObjectDeserializationError(stream); SceneObjectsFactory::HandleObjectDeserializationError(stream);
} }
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, data, modifier.Value); SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, data, modifier.Value);
if (withSynchronization) if (options.WithSync)
{ {
// Synchronize new prefab instances (prefab may have new objects added so deserialized instances need to synchronize with it) // Synchronize new prefab instances (prefab may have new objects added so deserialized instances need to synchronize with it)
// TODO: resave and force sync prefabs during game cooking so this step could be skipped in game // TODO: resave and force sync prefabs during game cooking so this step could be skipped in game
@@ -157,7 +190,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
Scripting::ObjectsLookupIdMapping.Set(prevIdMapping); Scripting::ObjectsLookupIdMapping.Set(prevIdMapping);
// Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it) // Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it)
if (withSynchronization) if (options.WithSync)
{ {
// TODO: resave and force sync scenes during game cooking so this step could be skipped in game // TODO: resave and force sync scenes during game cooking so this step could be skipped in game
SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData); SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData);
@@ -190,13 +223,13 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
// Prepare parent linkage for prefab root actor // Prepare parent linkage for prefab root actor
if (root->_parent) if (root->_parent)
root->_parent->Children.Remove(root); root->_parent->Children.Remove(root);
root->_parent = parent; root->_parent = options.Parent;
if (parent) if (options.Parent)
parent->Children.Add(root); options.Parent->Children.Add(root);
// Move root to the right location // Move root to the right location
if (transform.Translation != Vector3::Minimum) if (options.Transform)
root->SetTransform(transform); root->SetTransform(*options.Transform);
// Link actors hierarchy // Link actors hierarchy
for (int32 i = 0; i < sceneObjects->Count(); i++) for (int32 i = 0; i < sceneObjects->Count(); i++)
@@ -268,24 +301,39 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
} }
// Link objects to prefab (only deserialized from prefab data) // Link objects to prefab (only deserialized from prefab data)
for (int32 i = 0; i < dataCount; i++) if (options.WithLink)
{ {
auto& stream = data[i]; for (int32 i = 0; i < dataCount; i++)
SceneObject* obj = sceneObjects->At(i); {
if (!obj) auto& stream = data[i];
continue; SceneObject* obj = sceneObjects->At(i);
if (!obj)
continue;
const Guid prefabObjectId = JsonTools::GetGuid(stream, "ID"); const Guid prefabObjectId = JsonTools::GetGuid(stream, "ID");
if (objectsCache) if (options.ObjectsCache)
objectsCache->Add(prefabObjectId, obj); options.ObjectsCache->Add(prefabObjectId, obj);
obj->LinkPrefab(prefabId, prefabObjectId); obj->LinkPrefab(prefabId, prefabObjectId);
}
}
else if (options.ObjectsCache)
{
for (int32 i = 0; i < dataCount; i++)
{
auto& stream = data[i];
SceneObject* obj = sceneObjects->At(i);
if (!obj)
continue;
const Guid prefabObjectId = JsonTools::GetGuid(stream, "ID");
options.ObjectsCache->Add(prefabObjectId, obj);
}
} }
// Update transformations // Update transformations
root->OnTransformChanged(); root->OnTransformChanged();
// Spawn if need to // Spawn if need to
if (parent && parent->IsDuringPlay()) if (options.Parent && options.Parent->IsDuringPlay())
{ {
// Begin play // Begin play
SceneBeginData beginData; SceneBeginData beginData;

View File

@@ -102,6 +102,30 @@ API_CLASS(Static) class FLAXENGINE_API PrefabManager
/// <returns>The created actor (root) or null if failed.</returns> /// <returns>The created actor (root) or null if failed.</returns>
static Actor* SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, SceneObject*, HeapAllocation>* objectsCache, bool withSynchronization = true); static Actor* SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, SceneObject*, HeapAllocation>* objectsCache, bool withSynchronization = true);
struct FLAXENGINE_API SpawnOptions
{
// Spawn transformation.
const Transform* Transform = nullptr;
// The parent actor to add spawned object instance. Can be null to just deserialize contents of the prefab.
Actor* Parent = nullptr;
// Custom objects mapping (maps prefab objects into spawned objects).
const Dictionary<Guid, Guid, HeapAllocation>* IDs = nullptr;
// Output objects cache that can be filled with prefab object id mapping to deserialized object (actor or script).
Dictionary<Guid, SceneObject*, HeapAllocation>* ObjectsCache = nullptr;
// if perform prefab changes synchronization for the spawned objects. It will check if need to add new objects due to nested prefab modifications.
bool WithSync = true;
// True if linked spawned prefab objects with the source prefab, otherwise links will be valid only for nested prefab objects.
bool WithLink = true;
};
/// <summary>
/// Spawns the instance of the prefab objects. If parent actor is specified then created actors are fully initialized (OnLoad event and BeginPlay is called if parent actor is already during gameplay).
/// </summary>
/// <param name="prefab">The prefab asset.</param>
/// <param name="options">The spawn options container.</param>
/// <returns>The created actor (root) or null if failed.</returns>
static Actor* SpawnPrefab(Prefab* prefab, const SpawnOptions& options);
#if USE_EDITOR #if USE_EDITOR
/// <summary> /// <summary>