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->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)
@@ -235,14 +247,7 @@ void PrefabInstanceData::SerializePrefabInstances(PrefabInstancesData& prefabIns
// Serialize
tmpBuffer.Clear();
CompactJsonWriter writerObj(tmpBuffer);
JsonWriter& writer = writerObj;
writer.StartArray();
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects.Value->At(i);
writer.SceneObject(obj);
}
writer.EndArray();
SerializeObjects(*sceneObjects.Value, writerObj);
// Parse json to get DOM
{
@@ -746,14 +751,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
rapidjson_flax::StringBuffer dataBuffer;
{
CompactJsonWriter writerObj(dataBuffer);
JsonWriter& writer = writerObj;
writer.StartArray();
for (int32 i = 0; i < targetObjects->Count(); i++)
{
SceneObject* obj = targetObjects.Value->At(i);
writer.SceneObject(obj);
}
writer.EndArray();
PrefabInstanceData::SerializeObjects(*targetObjects.Value, writerObj);
}
// 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();
PrettyJsonWriter writerObj(tmpBuffer);
JsonWriter& writer = writerObj;
writer.StartArray();
for (int32 i = 0; i < defaultInstanceObjects.Count(); i++)
{
auto obj = defaultInstanceObjects.At(i);
writer.SceneObject(obj);
}
writer.EndArray();
PrefabInstanceData::SerializeObjects(defaultInstanceObjects, writerObj);
}
LOG(Info, "Updating prefab data");

View File

@@ -40,7 +40,9 @@ PrefabManagerService PrefabManagerServiceInstance;
Actor* PrefabManager::SpawnPrefab(Prefab* prefab)
{
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)
@@ -69,20 +71,39 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, 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)
{
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)
{
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)
{
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");
if (prefab == nullptr)
@@ -111,15 +132,27 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
sceneObjects->Resize(dataCount);
CollectionPoolCache<ISerializeModifier, Cache::ISerializeModifierClearCallback>::ScopeCache modifier = Cache::ISerializeModifier.Get();
modifier->EngineBuild = prefab->DataEngineBuild;
for (int32 i = 0; i < prefab->ObjectsIds.Count(); i++)
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();
objectsCache->SetCapacity(prefab->ObjectsDataCache.Capacity());
for (int32 i = 0; i < prefab->ObjectsIds.Count(); i++)
modifier->IdsMapping.Add(prefab->ObjectsIds[i], Guid::New());
}
if (options.ObjectsCache)
{
options.ObjectsCache->Clear();
options.ObjectsCache->SetCapacity(prefab->ObjectsDataCache.Count());
}
auto& data = *prefab->Data;
SceneObjectsFactory::Context context(modifier.Value);
@@ -139,7 +172,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
SceneObjectsFactory::HandleObjectDeserializationError(stream);
}
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)
// 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);
// 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
SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData);
@@ -190,13 +223,13 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
// Prepare parent linkage for prefab root actor
if (root->_parent)
root->_parent->Children.Remove(root);
root->_parent = parent;
if (parent)
parent->Children.Add(root);
root->_parent = options.Parent;
if (options.Parent)
options.Parent->Children.Add(root);
// Move root to the right location
if (transform.Translation != Vector3::Minimum)
root->SetTransform(transform);
if (options.Transform)
root->SetTransform(*options.Transform);
// Link actors hierarchy
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)
for (int32 i = 0; i < dataCount; i++)
if (options.WithLink)
{
auto& stream = data[i];
SceneObject* obj = sceneObjects->At(i);
if (!obj)
continue;
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");
if (objectsCache)
objectsCache->Add(prefabObjectId, obj);
obj->LinkPrefab(prefabId, prefabObjectId);
const Guid prefabObjectId = JsonTools::GetGuid(stream, "ID");
if (options.ObjectsCache)
options.ObjectsCache->Add(prefabObjectId, obj);
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
root->OnTransformChanged();
// Spawn if need to
if (parent && parent->IsDuringPlay())
if (options.Parent && options.Parent->IsDuringPlay())
{
// Begin play
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>
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
/// <summary>