Add content deprecation upgrades support to prefabs and scenes when loading levels

This commit is contained in:
Wojtek Figat
2025-01-20 23:53:13 +01:00
parent 67f12596e2
commit 7d0804af91
7 changed files with 113 additions and 7 deletions

View File

@@ -7,6 +7,7 @@
#include "SceneObjectsFactory.h"
#include "Scene/Scene.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Deprecated.h"
#include "Engine/Core/Cache.h"
#include "Engine/Core/Collections/CollectionPoolCache.h"
#include "Engine/Core/ObjectsRemovalService.h"
@@ -827,7 +828,7 @@ bool Level::loadScene(JsonAsset* sceneAsset)
return true;
}
return loadScene(*sceneAsset->Data, sceneAsset->DataEngineBuild);
return loadScene(*sceneAsset->Data, sceneAsset->DataEngineBuild, nullptr, &sceneAsset->GetPath());
}
bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene)
@@ -866,11 +867,14 @@ bool Level::loadScene(rapidjson_flax::Document& document, Scene** outScene)
return loadScene(data->value, saveEngineBuild, outScene);
}
bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene)
bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath)
{
PROFILE_CPU_NAMED("Level.LoadScene");
if (outScene)
*outScene = nullptr;
#if USE_EDITOR
ContentDeprecated::Clear();
#endif
LOG(Info, "Loading scene...");
Stopwatch stopwatch;
_lastSceneLoadTime = DateTime::Now();
@@ -1002,6 +1006,9 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
if (context.Async)
{
ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize)
#if USE_EDITOR
volatile int64 deprecated = 0;
#endif
JobSystem::Execute([&](int32 i)
{
i++; // Start from 1. at index [0] was scene
@@ -1011,9 +1018,17 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
auto& idMapping = Scripting::ObjectsLookupIdMapping.Get();
idMapping = &context.GetModifier()->IdsMapping;
SceneObjectsFactory::Deserialize(context, obj, data[i]);
#if USE_EDITOR
if (ContentDeprecated::Clear())
Platform::InterlockedIncrement(&deprecated);
#endif
idMapping = nullptr;
}
}, dataCount - 1);
#if USE_EDITOR
if (deprecated != 0)
ContentDeprecated::Mark();
#endif
ScenesLock.Lock();
}
else
@@ -1103,6 +1118,28 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
LOG(Info, "Scene loaded in {0}ms", stopwatch.GetMilliseconds());
if (outScene)
*outScene = scene;
#if USE_EDITOR
// Resave assets that use deprecated data format
for (auto& e : context.DeprecatedPrefabs)
{
AssetReference<Prefab> prefab = e.Item;
LOG(Info, "Resaving asset '{}' that uses deprecated data format", prefab->GetPath());
if (prefab->Resave())
{
LOG(Error, "Failed to resave asset '{}'", prefab->GetPath());
}
}
if (ContentDeprecated::Clear() && assetPath)
{
LOG(Info, "Resaving asset '{}' that uses deprecated data format", *assetPath);
if (saveScene(scene, *assetPath))
{
LOG(Error, "Failed to resave asset '{}'", *assetPath);
}
}
#endif
return false;
}
@@ -1125,6 +1162,7 @@ bool LevelImpl::saveScene(Scene* scene)
bool LevelImpl::saveScene(Scene* scene, const String& path)
{
PROFILE_CPU_NAMED("Level.SaveScene");
ASSERT(scene && EnumHasNoneFlags(scene->Flags, ObjectFlags::WasMarkedToDelete));
auto sceneId = scene->GetID();

View File

@@ -551,5 +551,5 @@ private:
static bool loadScene(JsonAsset* sceneAsset);
static bool loadScene(const BytesContainer& sceneData, Scene** outScene = nullptr);
static bool loadScene(rapidjson_flax::Document& document, Scene** outScene = nullptr);
static bool loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene = nullptr);
static bool loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene = nullptr, const String* assetPath = nullptr);
};

View File

@@ -732,6 +732,50 @@ bool Prefab::ApplyAll(Actor* targetActor)
return false;
}
bool Prefab::Resave()
{
if (OnCheckSave())
return true;
PROFILE_CPU_NAMED("Prefab.Resave");
ScopeLock lock(Locker);
Dictionary<Guid, Guid> objectIds;
objectIds.EnsureCapacity(ObjectsIds.Count());
for (int32 i = 0; i < ObjectsIds.Count(); i++)
{
Guid id = ObjectsIds[i];
objectIds.Add(id, id);
}
PrefabManager::SpawnOptions options;
options.WithLink = false;
options.IDs = &objectIds;
auto instance = PrefabManager::SpawnPrefab(this, options);
if (instance == nullptr)
return true;
// Serialize to json data
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
SceneQuery::GetAllSerializableSceneObjects(instance, *sceneObjects.Value);
rapidjson_flax::StringBuffer dataBuffer;
{
PrettyJsonWriter writerObj(dataBuffer);
PrefabInstanceData::SerializeObjects(*sceneObjects.Value, writerObj);
}
// Remove temporary objects
instance->DeleteObject();
instance = nullptr;
// Save to file
if (CreateJson::Create(GetPath(), dataBuffer, TypeName))
{
LOG(Warning, "Failed to serialize prefab data to the asset.");
return true;
}
return false;
}
bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData)
{
PROFILE_CPU_NAMED("Prefab.Apply");

View File

@@ -10,7 +10,7 @@ class Actor;
class SceneObject;
/// <summary>
/// Json asset that stores the collection of scene objects including actors and scripts. In general it can serve as any grouping of scene objects (for example a level) or be used as a form of a template instantiated and reused throughout the scene.
/// Json asset that stores the collection of scene objects including actors and scripts. In general, it can serve as any grouping of scene objects (for example a level) or be used as a form of a template instantiated and reused throughout the scene.
/// </summary>
/// <seealso cref="JsonAssetBase" />
API_CLASS(NoSpawn) class FLAXENGINE_API Prefab : public JsonAssetBase
@@ -74,11 +74,16 @@ public:
/// <summary>
/// Applies the difference from the prefab object instance, saves the changes and synchronizes them with the active instances of the prefab asset.
/// </summary>
/// <remarks>
/// Applies all the changes from not only the given actor instance but all actors created within that prefab instance.
/// </remarks>
/// <remarks>Applies all the changes from not only the given actor instance but all actors created within that prefab instance.</remarks>
/// <param name="targetActor">The root actor of spawned prefab instance to use as modified changes sources.</param>
/// <returns>True if failed, otherwise false.</returns>
bool ApplyAll(Actor* targetActor);
/// <summary>
/// Resaves the prefab asset to the file by serializing default instance in the latest format and defaults.
/// </summary>
/// <returns>True if failed, otherwise false.</returns>
API_FUNCTION() bool Resave();
#endif
private:

View File

@@ -5,6 +5,7 @@
#include "Engine/Level/Level.h"
#include "Engine/Content/AssetInfo.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Deprecated.h"
#include "Engine/Content/Factories/JsonAssetFactory.h"
#include "Engine/Physics/Colliders/MeshCollider.h"
#include "Engine/Level/Actors/StaticModel.h"
@@ -306,6 +307,7 @@ void Scene::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
if (e != stream.MemberEnd())
{
// Upgrade from old single hidden navmesh data into NavMesh actors on a scene
MARK_CONTENT_DEPRECATED();
AssetReference<RawDataAsset> dataAsset;
Serialization::Deserialize(e->value, dataAsset, modifier);
const auto settings = NavigationSettings::Get();

View File

@@ -19,6 +19,7 @@
#include "Engine/Threading/Threading.h"
#include "Engine/Level/Scripts/MissingScript.h"
#endif
#include "Engine/Content/Deprecated.h"
#include "Engine/Level/Scripts/ModelPrefab.h"
#if USE_EDITOR
@@ -199,6 +200,7 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, const ISerializable::D
else
{
// [Deprecated: 18.07.2019 expires 18.07.2020]
MARK_CONTENT_DEPRECATED();
const auto typeIdMember = stream.FindMember("TypeID");
if (typeIdMember == stream.MemberEnd())
{
@@ -286,7 +288,19 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria
// Deserialize prefab data (recursive prefab loading to support nested prefabs)
const auto prevVersion = modifier->EngineBuild;
modifier->EngineBuild = prefab->DataEngineBuild;
#if USE_EDITOR
bool prevDeprecated = ContentDeprecated::Clear();
#endif
Deserialize(context, obj, *(ISerializable::DeserializeStream*)prefabData);
#if USE_EDITOR
if (ContentDeprecated::Clear(prevDeprecated))
{
// Prefab contains deprecated data format
context.Locker.Lock();
context.DeprecatedPrefabs.Add(prefab);
context.Locker.Unlock();
}
#endif
modifier->EngineBuild = prevVersion;
}

View File

@@ -31,6 +31,9 @@ public:
Dictionary<Guid, int32> ObjectToInstance;
CriticalSection Locker;
ThreadLocal<ISerializeModifier*> Modifiers;
#if USE_EDITOR
HashSet<Prefab*> DeprecatedPrefabs;
#endif
Context(ISerializeModifier* modifier);
~Context();