script and actors not having connect transform in on awake and in on start join auto anchor having wrong anchor because was including scale fixed selection do to changes to transform accessibility ?
204 lines
5.7 KiB
C++
204 lines
5.7 KiB
C++
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
|
|
|
#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"
|
|
#include "Engine/Level/Actor.h"
|
|
#include "Engine/Threading/Threading.h"
|
|
#if USE_EDITOR
|
|
#include "Engine/Scripting/Scripting.h"
|
|
#endif
|
|
|
|
REGISTER_JSON_ASSET(Prefab, "FlaxEngine.Prefab", true);
|
|
|
|
Prefab::Prefab(const SpawnParams& params, const AssetInfo* info)
|
|
: JsonAssetBase(params, info)
|
|
, _isCreatingDefaultInstance(false)
|
|
, _defaultInstance(nullptr)
|
|
, ObjectsCount(0)
|
|
{
|
|
}
|
|
|
|
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);
|
|
|
|
// Skip if already created (reuse cached result)
|
|
if (_defaultInstance)
|
|
return _defaultInstance;
|
|
|
|
// Skip if not loaded
|
|
if (!IsLoaded())
|
|
{
|
|
LOG(Warning, "Cannot instantiate object from not loaded prefab asset.");
|
|
return nullptr;
|
|
}
|
|
|
|
// Prevent recursive calls
|
|
if (_isCreatingDefaultInstance)
|
|
{
|
|
LOG(Warning, "Loop call to Prefab::GetDefaultInstance.");
|
|
return nullptr;
|
|
}
|
|
_isCreatingDefaultInstance = true;
|
|
|
|
// Instantiate objects from prefab (default spawning logic)
|
|
_defaultInstance = PrefabManager::SpawnPrefab(this, Transform::Identity, nullptr, &ObjectsCache);
|
|
|
|
_isCreatingDefaultInstance = false;
|
|
return _defaultInstance;
|
|
}
|
|
|
|
SceneObject* Prefab::GetDefaultInstance(const Guid& objectId)
|
|
{
|
|
const auto result = GetDefaultInstance();
|
|
if (!result)
|
|
return nullptr;
|
|
|
|
if (objectId.IsValid())
|
|
{
|
|
const void* object;
|
|
if (ObjectsCache.TryGet(objectId, object))
|
|
{
|
|
// Actor or Script
|
|
return (SceneObject*)object;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Prefab::DeleteDefaultInstance()
|
|
{
|
|
ScopeLock lock(Locker);
|
|
ObjectsCache.Clear();
|
|
if (_defaultInstance)
|
|
{
|
|
_defaultInstance->DeleteObject();
|
|
_defaultInstance = nullptr;
|
|
}
|
|
}
|
|
|
|
Asset::LoadResult Prefab::loadAsset()
|
|
{
|
|
// Base
|
|
const auto result = JsonAssetBase::loadAsset();
|
|
if (result != LoadResult::Ok)
|
|
return result;
|
|
|
|
// Validate data schema
|
|
if (!Data->IsArray())
|
|
{
|
|
LOG(Warning, "Invalid prefab data.");
|
|
return LoadResult::InvalidData;
|
|
}
|
|
|
|
// Get objects amount
|
|
const int32 objectsCount = Data->GetArray().Size();
|
|
if (objectsCount <= 0)
|
|
{
|
|
LOG(Warning, "Prefab is empty or has invalid amount of objects.");
|
|
return LoadResult::InvalidData;
|
|
}
|
|
|
|
// Allocate memory for objects
|
|
ObjectsIds.EnsureCapacity(objectsCount * 2);
|
|
ObjectsDataCache.EnsureCapacity(objectsCount * 3);
|
|
|
|
// Find serialized object ids (actors and scripts), they are used later for IDs mapping on prefab spawning via PrefabManager
|
|
const auto& data = *Data;
|
|
for (int32 objectIndex = 0; objectIndex < objectsCount; objectIndex++)
|
|
{
|
|
auto& objData = data[objectIndex];
|
|
|
|
Guid objectId = JsonTools::GetGuid(objData, "ID");
|
|
if (!objectId.IsValid())
|
|
{
|
|
LOG(Warning, "The object inside prefab has invalid ID.");
|
|
return LoadResult::InvalidData;
|
|
}
|
|
|
|
ObjectsIds.Add(objectId);
|
|
ObjectsDataCache.Add(objectId, &objData);
|
|
ObjectsCount++;
|
|
|
|
Guid prefabId = JsonTools::GetGuid(objData, "PrefabID");
|
|
if (prefabId.IsValid() && !NestedPrefabs.Contains(prefabId))
|
|
{
|
|
if (prefabId == _id)
|
|
{
|
|
LOG(Error, "Circural reference in prefab.");
|
|
return LoadResult::InvalidData;
|
|
}
|
|
|
|
NestedPrefabs.Add(prefabId);
|
|
}
|
|
}
|
|
|
|
#if USE_EDITOR
|
|
// Register for scripts reload and unload (need to cleanup all user objects including scripts that may be attached to the default instance - it can be always restored)
|
|
Scripting::ScriptsReloading.Bind<Prefab, &Prefab::DeleteDefaultInstance>(this);
|
|
Scripting::ScriptsUnload.Bind<Prefab, &Prefab::DeleteDefaultInstance>(this);
|
|
#endif
|
|
|
|
return LoadResult::Ok;
|
|
}
|
|
|
|
void Prefab::unload(bool isReloading)
|
|
{
|
|
#if USE_EDITOR
|
|
// Unlink
|
|
Scripting::ScriptsReloading.Unbind<Prefab, &Prefab::DeleteDefaultInstance>(this);
|
|
Scripting::ScriptsUnload.Unbind<Prefab, &Prefab::DeleteDefaultInstance>(this);
|
|
#endif
|
|
|
|
// Base
|
|
JsonAssetBase::unload(isReloading);
|
|
|
|
ObjectsCount = 0;
|
|
ObjectsIds.Resize(0);
|
|
NestedPrefabs.Resize(0);
|
|
ObjectsDataCache.Clear();
|
|
ObjectsDataCache.SetCapacity(0);
|
|
ObjectsCache.Clear();
|
|
ObjectsCache.SetCapacity(0);
|
|
if (_defaultInstance)
|
|
{
|
|
_defaultInstance->DeleteObject();
|
|
_defaultInstance = nullptr;
|
|
}
|
|
}
|