Merge remote-tracking branch 'origin/master' into 1.6

This commit is contained in:
Wojtek Figat
2023-06-11 21:38:20 +02:00
20 changed files with 212 additions and 93 deletions

View File

@@ -242,21 +242,36 @@ namespace FlaxEditor.Content.Thumbnails
if (!atlas.IsReady)
return;
// Setup
_guiRoot.RemoveChildren();
_guiRoot.AccentColor = request.Proxy.AccentColor;
try
{
// Setup
_guiRoot.RemoveChildren();
_guiRoot.AccentColor = request.Proxy.AccentColor;
// Call proxy to prepare for thumbnail rendering
request.Proxy.OnThumbnailDrawBegin(request, _guiRoot, context);
_guiRoot.UnlockChildrenRecursive();
// Call proxy to prepare for thumbnail rendering
request.Proxy.OnThumbnailDrawBegin(request, _guiRoot, context);
_guiRoot.UnlockChildrenRecursive();
// Draw preview
context.Clear(_output.View(), Color.Black);
Render2D.CallDrawing(_guiRoot, context, _output);
// Draw preview
context.Clear(_output.View(), Color.Black);
Render2D.CallDrawing(_guiRoot, context, _output);
// Call proxy and cleanup UI (delete create controls, shared controls should be unlinked during OnThumbnailDrawEnd event)
request.Proxy.OnThumbnailDrawEnd(request, _guiRoot);
_guiRoot.DisposeChildren();
// Call proxy and cleanup UI (delete create controls, shared controls should be unlinked during OnThumbnailDrawEnd event)
request.Proxy.OnThumbnailDrawEnd(request, _guiRoot);
}
catch (Exception ex)
{
// Handle internal errors gracefully (eg. when asset is corrupted and proxy fails)
Editor.LogError("Failed to render thumbnail icon for asset: " + request.Item);
Editor.LogWarning(ex);
request.FinishRender(ref SpriteHandle.Invalid);
RemoveRequest(request);
return;
}
finally
{
_guiRoot.DisposeChildren();
}
// Copy backbuffer with rendered preview into atlas
SpriteHandle icon = atlas.OccupySlot(_output, request.Item.ID);

View File

@@ -207,7 +207,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
node.Text = Utilities.Utils.GetPropertyNameUI(sceneObject.GetType().Name);
}
// Array Item
else if (editor.ParentEditor?.Values?.Type.IsArray ?? false)
else if (editor.ParentEditor is CollectionEditor)
{
node.Text = "Element " + editor.ParentEditor.ChildrenEditors.IndexOf(editor);
}
@@ -250,16 +250,14 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
// Skip if no change detected
if (!editor.Values.IsReferenceValueModified && skipIfNotModified)
var isRefEdited = editor.Values.IsReferenceValueModified;
if (!isRefEdited && skipIfNotModified)
return null;
TreeNode result = null;
if (editor.ChildrenEditors.Count == 0)
if (editor.ChildrenEditors.Count == 0 || (isRefEdited && editor is CollectionEditor))
result = CreateDiffNode(editor);
bool isScriptEditorWithRefValue = editor is ScriptsEditor && editor.Values.HasReferenceValue;
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
{
var child = ProcessDiff(editor.ChildrenEditors[i], !isScriptEditorWithRefValue);
@@ -267,7 +265,6 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
if (result == null)
result = CreateDiffNode(editor);
result.AddChild(child);
}
}

View File

@@ -51,7 +51,7 @@ namespace FlaxEditor.CustomEditors
if (values.HasReferenceValue)
{
if (values.ReferenceValue is IList v && values.Count == v.Count && v.Count > index)
if (values.ReferenceValue is IList v && v.Count > index)
{
_referenceValue = v[index];
_hasReferenceValue = true;

View File

@@ -1012,7 +1012,7 @@ Asset::LoadResult Model::load()
}
}
#if BUILD_DEBUG || BUILD_DEVELOPMENT
#if !BUILD_RELEASE
// Validate LODs
for (int32 lodIndex = 1; lodIndex < LODs.Count(); lodIndex++)
{

View File

@@ -304,7 +304,7 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
auto storage = ContentStorageManager::GetStorage(path);
if (storage)
{
#if BUILD_DEBUG
#if BUILD_DEBUG || FLAX_TESTS
ASSERT(storage->GetPath() == path);
#endif
@@ -909,7 +909,7 @@ bool Content::IsAssetTypeIdInvalid(const ScriptingTypeHandle& type, const Script
if (!type || !assetType)
return false;
#if BUILD_DEBUG
#if BUILD_DEBUG || FLAX_TESTS
// Peek types for debugging
const auto& typeObj = type.GetType();
const auto& assetTypeObj = assetType.GetType();

View File

@@ -419,7 +419,7 @@ void StaticModel::Serialize(SerializeStream& stream, const void* otherObj)
if (HasLightmap()
#if USE_EDITOR
&& PrefabManager::IsNotCreatingPrefab
&& !PrefabManager::IsCreatingPrefab
#endif
)
{

View File

@@ -336,7 +336,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
continue;
}
SceneObject* obj = SceneObjectsFactory::Spawn(context, *(ISerializable::DeserializeStream*)data);
SceneObject* obj = SceneObjectsFactory::Spawn(context, *data);
if (!obj)
continue;
obj->RegisterObject();

View File

@@ -2,6 +2,7 @@
#include "Prefab.h"
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Factories/JsonAssetFactory.h"
#include "Engine/Core/Log.h"
#include "Engine/Level/Prefabs/PrefabManager.h"
@@ -21,6 +22,36 @@ Prefab::Prefab(const SpawnParams& params, const AssetInfo* info)
{
}
Guid Prefab::GetRootObjectId() const
{
ASSERT(IsLoaded());
ScopeLock lock(Locker);
// Root is always the first but handle case when prefab root was reordered in the base prefab while the nested prefab has still the old state
// TODO: resave and force sync prefabs during game cooking so this step could be skipped in game
int32 objectIndex = 0;
if (NestedPrefabs.HasItems())
{
const auto& data = *Data;
const Guid basePrefabId = JsonTools::GetGuid(data[objectIndex], "PrefabID");
if (const auto basePrefab = Content::Load<Prefab>(basePrefabId))
{
const Guid basePrefabRootId = basePrefab->GetRootObjectId();
for (int32 i = 0; i < ObjectsCount; i++)
{
const Guid prefabObjectId = JsonTools::GetGuid(data[i], "PrefabObjectID");
if (prefabObjectId == basePrefabRootId)
{
objectIndex = i;
break;
}
}
}
}
return ObjectsIds[objectIndex];
}
Actor* Prefab::GetDefaultInstance()
{
ScopeLock lock(Locker);
@@ -105,7 +136,6 @@ Asset::LoadResult Prefab::loadAsset()
// Allocate memory for objects
ObjectsIds.EnsureCapacity(objectsCount * 2);
NestedPrefabs.EnsureCapacity(objectsCount);
ObjectsDataCache.EnsureCapacity(objectsCount * 3);
// Find serialized object ids (actors and scripts), they are used later for IDs mapping on prefab spawning via PrefabManager
@@ -122,7 +152,6 @@ Asset::LoadResult Prefab::loadAsset()
}
ObjectsIds.Add(objectId);
ASSERT(!ObjectsDataCache.ContainsKey(objectId));
ObjectsDataCache.Add(objectId, &objData);
ObjectsCount++;

View File

@@ -50,11 +50,7 @@ public:
/// <summary>
/// Gets the root object identifier (prefab object ID). Asset must be loaded.
/// </summary>
Guid GetRootObjectId() const
{
ASSERT(IsLoaded());
return ObjectsIds[0];
}
Guid GetRootObjectId() const;
/// <summary>
/// Requests the default prefab object instance. Deserializes the prefab objects from the asset. Skips if already done.

View File

@@ -21,7 +21,6 @@
#if USE_EDITOR
bool PrefabManager::IsCreatingPrefab = false;
bool PrefabManager::IsNotCreatingPrefab = true;
Dictionary<Guid, Array<Actor*>> PrefabManager::PrefabsReferences;
CriticalSection PrefabManager::PrefabsReferencesLocker;
#endif
@@ -39,7 +38,7 @@ PrefabManagerService PrefabManagerServiceInstance;
Actor* PrefabManager::SpawnPrefab(Prefab* prefab)
{
Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes[0] : nullptr;
Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr;
return SpawnPrefab(prefab, parent, nullptr);
}
@@ -47,9 +46,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position)
{
auto instance = SpawnPrefab(prefab);
if (instance)
{
instance->SetPosition(position);
}
return instance;
}
@@ -57,12 +54,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position, const
{
auto instance = SpawnPrefab(prefab);
if (instance)
{
auto transform = instance->GetTransform();
transform.Translation = position;
transform.Orientation = rotation;
instance->SetTransform(transform);
}
instance->SetTransform(Transform(position, rotation, instance->GetScale()));
return instance;
}
@@ -70,13 +62,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position, const
{
auto instance = SpawnPrefab(prefab);
if (instance)
{
Transform transform;
transform.Translation = position;
transform.Orientation = rotation;
transform.Scale = scale;
instance->SetTransform(transform);
}
instance->SetTransform(Transform(position, rotation, scale));
return instance;
}
@@ -84,17 +70,13 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform)
{
auto instance = SpawnPrefab(prefab);
if (instance)
{
instance->SetTransform(transform);
}
return instance;
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, const void*>* objectsCache, bool withSynchronization)
{
PROFILE_CPU_NAMED("Prefab.Spawn");
// Validate input
if (prefab == nullptr)
{
Log::ArgumentNullException();
@@ -111,12 +93,11 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid
LOG(Warning, "Prefab has no objects. {0}", prefab->ToString());
return nullptr;
}
const Guid prefabId = prefab->GetID();
// Note: we need to generate unique Ids for the deserialized objects (actors and scripts) to prevent Ids collisions
// Prefab asset during loading caches the object Ids stored inside the file
const Guid prefabId = prefab->GetID();
// Prepare
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
sceneObjects->Resize(objectsCount);
@@ -140,7 +121,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid
for (int32 i = 0; i < objectsCount; i++)
{
auto& stream = data[i];
auto obj = SceneObjectsFactory::Spawn(context, stream);
SceneObject* obj = SceneObjectsFactory::Spawn(context, stream);
sceneObjects->At(i) = obj;
if (obj)
obj->RegisterObject();
@@ -165,16 +146,25 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid
}
Scripting::ObjectsLookupIdMapping.Set(prevIdMapping);
// Assume that prefab has always only one root actor that is serialized first
if (sceneObjects.Value->IsEmpty())
// Pick prefab root object
if (sceneObjects->IsEmpty())
{
LOG(Warning, "No valid objects in prefab.");
return nullptr;
}
auto root = (Actor*)sceneObjects.Value->At(0);
Actor* root = nullptr;
const Guid prefabRootObjectId = prefab->GetRootObjectId();
for (int32 i = 0; i < objectsCount; i++)
{
if (JsonTools::GetGuid(data[i], "ID") == prefabRootObjectId)
{
root = dynamic_cast<Actor*>(sceneObjects->At(i));
break;
}
}
if (!root)
{
LOG(Warning, "Failed to load prefab root object.");
LOG(Warning, "Missing prefab root object.");
return nullptr;
}
@@ -186,6 +176,8 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid
}
// Prepare parent linkage for prefab root actor
if (root->_parent)
root->_parent->Children.Remove(root);
root->_parent = parent;
if (parent)
parent->Children.Add(root);
@@ -193,41 +185,41 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid
// Link actors hierarchy
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
auto obj = sceneObjects->At(i);
SceneObject* obj = sceneObjects->At(i);
if (obj)
obj->Initialize();
}
// Delete objects without parent or with invalid linkage to the prefab
for (int32 i = 1; i < sceneObjects->Count(); i++)
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects->At(i);
if (!obj)
if (!obj || obj == root)
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());
sceneObjects->At(i) = nullptr;
obj->DeleteObject();
continue;
}
#if USE_EDITOR && !BUILD_RELEASE
#if (USE_EDITOR && !BUILD_RELEASE) || FLAX_TESTS
// Check for not being added to the parent (eg. invalid setup events fault on registration)
auto actor = dynamic_cast<Actor*>(obj);
auto script = dynamic_cast<Script*>(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());
sceneObjects->At(i) = nullptr;
obj->DeleteObject();
continue;
}
#endif
#if USE_EDITOR && BUILD_DEBUG
#if (USE_EDITOR && BUILD_DEBUG) || FLAX_TESTS
// 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++)
@@ -240,11 +232,22 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid
}
if (!hasParentInInstance)
{
sceneObjects->At(i) = nullptr;
LOG(Warning, "Scene object {0} {1} has invalid parent object after load. Removing it.", obj->GetID(), obj->ToString());
sceneObjects->At(i) = nullptr;
obj->DeleteObject();
continue;
}
#if FLAX_TESTS
// Perform extensive validation of the prefab instance structure
if (actor && actor->HasActorInHierarchy(actor))
{
LOG(Warning, "Scene object {0} {1} has invalid hierarchy after load. Removing it.", obj->GetID(), obj->ToString());
sceneObjects->At(i) = nullptr;
obj->DeleteObject();
continue;
}
#endif
#endif
}
@@ -256,8 +259,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid
if (!obj)
continue;
const auto prefabObjectId = JsonTools::GetGuid(stream, "ID");
const Guid prefabObjectId = JsonTools::GetGuid(stream, "ID");
if (objectsCache)
objectsCache->Add(prefabObjectId, obj);
obj->LinkPrefab(prefabId, prefabObjectId);
@@ -316,8 +318,8 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat
LOG(Info, "Creating prefab from actor {0} (total objects count: {2}) to {1}...", targetActor->ToString(), outputPath, sceneObjects->Count());
// Serialize to json data
ASSERT(!IsCreatingPrefab);
IsCreatingPrefab = true;
IsNotCreatingPrefab = false;
rapidjson_flax::StringBuffer actorsDataBuffer;
{
CompactJsonWriter writerObj(actorsDataBuffer);
@@ -325,13 +327,12 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat
writer.StartArray();
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects.Value->At(i);
SceneObject* obj = sceneObjects->At(i);
writer.SceneObject(obj);
}
writer.EndArray();
}
IsCreatingPrefab = false;
IsNotCreatingPrefab = true;
// Randomize the objects ids (prevent overlapping of the prefab instance objects ids and the prefab objects ids)
Dictionary<Guid, Guid> objectInstanceIdToPrefabObjectId;
@@ -344,7 +345,7 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
// Generate new IDs for the prefab objects (other than reference instance used to create prefab)
const SceneObject* obj = sceneObjects.Value->At(i);
const SceneObject* obj = sceneObjects->At(i);
objectInstanceIdToPrefabObjectId.Add(obj->GetSceneObjectId(), Guid::New());
}
{
@@ -391,7 +392,7 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects.Value->At(i);
SceneObject* obj = sceneObjects->At(i);
Guid prefabObjectId;
if (objectInstanceIdToPrefabObjectId.TryGet(obj->GetSceneObjectId(), prefabObjectId))

View File

@@ -20,7 +20,7 @@ struct Transform;
/// </summary>
API_CLASS(Static) class FLAXENGINE_API PrefabManager
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(PrefabManager);
DECLARE_SCRIPTING_TYPE_NO_SPAWN(PrefabManager);
/// <summary>
/// Spawns the instance of the prefab objects. Prefab will be spawned to the first loaded scene.
@@ -92,11 +92,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(PrefabManager);
/// </summary>
static bool IsCreatingPrefab;
/// <summary>
/// Helper global state flag set to false during prefab asset creation. Can be used to skip local objects data serialization to prefab data.
/// </summary>
static bool IsNotCreatingPrefab;
/// <summary>
/// Creates the prefab asset from the given root actor. Saves it to the output file path.
/// </summary>

View File

@@ -58,6 +58,7 @@ API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API SceneObject : public Scripting
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(SceneObject);
friend PrefabInstanceData;
friend PrefabManager;
friend Actor;
friend Level;
friend ScriptsFactory;

View File

@@ -31,7 +31,7 @@ void SceneObjectsFactory::Context::SetupIdsMapping(const SceneObject* obj)
}
}
SceneObject* SceneObjectsFactory::Spawn(Context& context, ISerializable::DeserializeStream& stream)
SceneObject* SceneObjectsFactory::Spawn(Context& context, const ISerializable::DeserializeStream& stream)
{
// Get object id
Guid id = JsonTools::GetGuid(stream, "ID");
@@ -81,7 +81,7 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, ISerializable::Deseria
context.Modifier->IdsMapping[prefabObjectId] = id;
// Create prefab instance (recursive prefab loading to support nested prefabs)
obj = Spawn(context, *(ISerializable::DeserializeStream*)prefabData);
obj = Spawn(context, *prefabData);
}
else
{
@@ -584,7 +584,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabS
data.Modifier->IdsMapping[prefabObjectId] = id;
// Create prefab instance (recursive prefab loading to support nested prefabs)
auto child = Spawn(context, *(ISerializable::DeserializeStream*)prefabData);
auto child = Spawn(context, *prefabData);
if (!child)
{
LOG(Warning, "Failed to create object {1} from prefab {0}.", prefab->ToString(), prefabObjectId);

View File

@@ -35,7 +35,7 @@ public:
/// </summary>
/// <param name="context">The serialization context.</param>
/// <param name="stream">The serialized data stream.</param>
static SceneObject* Spawn(Context& context, ISerializable::DeserializeStream& stream);
static SceneObject* Spawn(Context& context, const ISerializable::DeserializeStream& stream);
/// <summary>
/// Deserializes the scene object from the specified data value.

View File

@@ -63,9 +63,7 @@ Asset::LoadResult LocalizedStringTable::loadAsset()
return result;
JsonTools::GetString(Locale, *Data, "Locale");
Guid fallbackTable = Guid::Empty;
JsonTools::GetGuid(fallbackTable, *Data, "FallbackTable");
FallbackTable = fallbackTable;
JsonTools::GetReference(FallbackTable, *Data, "FallbackTable");
const auto entriesMember = SERIALIZE_FIND_MEMBER((*Data), "Entries");
if (entriesMember != Data->MemberEnd() && entriesMember->value.IsObject())
{

View File

@@ -15,7 +15,7 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Scripting.h"
#define NETWORK_PROTOCOL_VERSION 1
#define NETWORK_PROTOCOL_VERSION 2
float NetworkManager::NetworkFPS = 60.0f;
NetworkPeer* NetworkManager::Peer = nullptr;

View File

@@ -95,6 +95,8 @@ PACK_STRUCT(struct NetworkMessageObjectRpc
{
NetworkMessageIDs ID = NetworkMessageIDs::ObjectRpc;
Guid ObjectId;
Guid ParentId;
char ObjectTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network)
char RpcTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network)
char RpcName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network)
uint16 ArgsSize;
@@ -1546,11 +1548,14 @@ void NetworkInternal::NetworkReplicatorUpdate()
//NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Rpc {}::{} object ID={}", e.Name.First.ToString(), String(e.Name.Second), item.ToString());
NetworkMessageObjectRpc msgData;
msgData.ObjectId = item.ObjectId;
msgData.ParentId = item.ParentId;
if (isClient)
{
// Remap local client object ids into server ids
IdsRemappingTable.KeyOf(msgData.ObjectId, &msgData.ObjectId);
IdsRemappingTable.KeyOf(msgData.ParentId, &msgData.ParentId);
}
GetNetworkName(msgData.ObjectTypeName, obj->GetType().Fullname);
GetNetworkName(msgData.RpcTypeName, e.Name.First.GetType().Fullname);
GetNetworkName(msgData.RpcName, e.Name.Second);
msgData.ArgsSize = (uint16)e.ArgsData.Length();
@@ -1949,7 +1954,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
NetworkMessageObjectRpc msgData;
event.Message.ReadStructure(msgData);
ScopeLock lock(ObjectsLock);
NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId);
NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId, msgData.ParentId, msgData.ObjectTypeName);
if (e)
{
auto& item = *e;

View File

@@ -156,7 +156,7 @@ public:
template<typename T>
static T* Cast(ScriptingObject* obj)
{
return obj && CanCast(obj->GetClass(), T::GetStaticClass()) ? (T*)obj : nullptr;
return obj && CanCast(obj->GetClass(), T::GetStaticClass()) ? static_cast<T*>(obj) : nullptr;
}
bool Is(const ScriptingTypeHandle& type) const;

View File

@@ -287,7 +287,7 @@ void TerrainChunk::Serialize(SerializeStream& stream, const void* otherObj)
if (HasLightmap()
#if USE_EDITOR
&& PrefabManager::IsNotCreatingPrefab
&& !PrefabManager::IsCreatingPrefab
#endif
)
{

View File

@@ -8,7 +8,6 @@
#include "Engine/Level/Prefabs/Prefab.h"
#include "Engine/Level/Prefabs/PrefabManager.h"
#include "Engine/Scripting/ScriptingObjectReference.h"
#include <ThirdParty/catch2/catch.hpp>
TEST_CASE("Prefabs")
@@ -330,4 +329,87 @@ TEST_CASE("Prefabs")
Content::DeleteAsset(nestedActorPrefab);
Content::DeleteAsset(testActorPrefab);
}
SECTION("Test Loading Nested Prefab After Changing Root")
{
// https://github.com/FlaxEngine/FlaxEngine/issues/1138
// Create base prefab with 3 objects
AssetReference<Prefab> prefabBase = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabBase);
Guid id;
Guid::Parse("2b3334524c696dcfa93cabacd2a4f404", id);
prefabBase->ChangeID(id);
auto prefabBaseInit = prefabBase->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\","
"\"TypeName\": \"FlaxEngine.DirectionalLight\","
"\"Name\": \"1\""
"},"
"{"
"\"ID\": \"589bcfaa4bd1a53435129480e5bbdb3b\","
"\"TypeName\": \"FlaxEngine.Camera\","
"\"ParentID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\","
"\"Name\": \"2\""
"},"
"{"
"\"ID\": \"9e81c24342e61af456411ea34593841d\","
"\"TypeName\": \"FlaxEngine.UICanvas\","
"\"ParentID\": \"589bcfaa4bd1a53435129480e5bbdb3b\","
"\"Name\": \"3\""
"}"
"]");
REQUIRE(!prefabBaseInit);
// Create nested prefab but with 'old' state where root object is different
AssetReference<Prefab> prefabNested = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabNested);
Guid::Parse("a71447e947cbd2deea018a8377636ce6", id);
prefabNested->ChangeID(id);
auto prefabNestedInit = prefabNested->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"597ab8ea43a5c58b8d06f58f9364d261\","
"\"PrefabID\": \"2b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"589bcfaa4bd1a53435129480e5bbdb3b\""
"},"
"{"
"\"ID\": \"1a6228d84897ff3b2f444ea263c3657e\","
"\"PrefabID\": \"2b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\","
"\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\""
"},"
"{"
"\"ID\": \"f8fbee1349f749396ab6c2ad34f3afec\","
"\"PrefabID\": \"2b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"9e81c24342e61af456411ea34593841d\","
"\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\""
"}"
"]");
REQUIRE(!prefabNestedInit);
// Spawn test instances of both prefabs
ScriptingObjectReference<Actor> instanceBase = PrefabManager::SpawnPrefab(prefabBase);
ScriptingObjectReference<Actor> instanceNested = PrefabManager::SpawnPrefab(prefabNested);
// Verify scenario
REQUIRE(instanceBase);
REQUIRE(instanceBase->GetName() == TEXT("1"));
REQUIRE(instanceBase->GetChildrenCount() == 1);
REQUIRE(instanceBase->Children[0]->GetName() == TEXT("2"));
REQUIRE(instanceBase->Children[0]->GetChildrenCount() == 1);
REQUIRE(instanceBase->Children[0]->Children[0]->GetName() == TEXT("3"));
REQUIRE(instanceNested);
REQUIRE(instanceNested->GetName() == TEXT("1"));
REQUIRE(instanceNested->GetChildrenCount() == 1);
REQUIRE(instanceNested->Children[0]->GetName() == TEXT("2"));
REQUIRE(instanceNested->Children[0]->GetChildrenCount() == 1);
REQUIRE(instanceNested->Children[0]->Children[0]->GetName() == TEXT("3"));
// Cleanup
instanceNested->DeleteObject();
instanceBase->DeleteObject();
Content::DeleteAsset(prefabNested);
Content::DeleteAsset(prefabBase);
}
}