Integrating up through commit 90f050496

This commit is contained in:
alexpete
2021-04-07 14:03:29 -07:00
parent 8f2ed080a9
commit c2cbd430fe
2694 changed files with 285622 additions and 176874 deletions
@@ -0,0 +1,58 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzToolsFramework
{
using EntityIdList = AZStd::vector<AZ::EntityId>;
/*!
* EditorEntityAPI
* Handles basic Entity operations
*/
class EditorEntityAPI
{
public:
AZ_RTTI(EditorEntityAPI, "{3E217E21-046F-462E-8FA2-1347FBDDFDE7}");
/**
* Delete all currently-selected entities.
*/
virtual void DeleteSelected() = 0;
/**
* Deletes the specified entity.
*/
virtual void DeleteEntityById(AZ::EntityId entityId) = 0;
/**
* Deletes all specified entities.
*/
virtual void DeleteEntities(const EntityIdList& entities) = 0;
/**
* Deletes the specified entity, as well as any transform descendants.
*/
virtual void DeleteEntityAndAllDescendants(AZ::EntityId entityId) = 0;
/**
* Deletes all entities in the provided list, as well as their transform descendants.
*/
virtual void DeleteEntitiesAndAllDescendants(const EntityIdList& entities) = 0;
};
} // namespace AzToolsFramework
@@ -588,13 +588,6 @@ namespace AzToolsFramework
*/
virtual const char* GetEngineVersion() const = 0;
/**
* Retrieves if Legacy Slice System is enabled
*/
virtual bool IsLegacySliceSystemEnabled() const = 0;
virtual bool ShouldAssertForLegacySlicesUsage() const = 0;
/**
* Creates and adds a new entity to the tools application from components which match at least one of the requiredTags
* The tag matching occurs on AZ::Edit::SystemComponentTags attribute from the reflected class data in the serialization context
@@ -0,0 +1,64 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Application/EditorEntityManager.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
namespace AzToolsFramework
{
void EditorEntityManager::Start()
{
m_prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get();
AZ_Assert(m_prefabPublicInterface, "EditorEntityManager - Could not retrieve instance of PrefabPublicInterface");
AZ::Interface<EditorEntityAPI>::Register(this);
}
EditorEntityManager::~EditorEntityManager()
{
// Attempting to Unregister if we never registerd (e.g. if Start() was never called) throws an error, so check that first
EditorEntityAPI* editorEntityInterface = AZ::Interface<EditorEntityAPI>::Get();
if (editorEntityInterface == this)
{
AZ::Interface<EditorEntityAPI>::Unregister(this);
}
}
void EditorEntityManager::DeleteSelected()
{
EntityIdList selectedEntities;
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
m_prefabPublicInterface->DeleteEntitiesInInstance(selectedEntities);
}
void EditorEntityManager::DeleteEntityById(AZ::EntityId entityId)
{
DeleteEntities({entityId});
}
void EditorEntityManager::DeleteEntities(const EntityIdList& entities)
{
m_prefabPublicInterface->DeleteEntitiesInInstance(entities);
}
void EditorEntityManager::DeleteEntityAndAllDescendants(AZ::EntityId entityId)
{
DeleteEntitiesAndAllDescendants({entityId});
}
void EditorEntityManager::DeleteEntitiesAndAllDescendants(const EntityIdList& entities)
{
m_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(entities);
}
}
@@ -0,0 +1,39 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzToolsFramework/API/EditorEntityAPI.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
namespace AzToolsFramework
{
class EditorEntityManager
: public EditorEntityAPI
{
public:
~EditorEntityManager();
void Start();
// EditorEntityAPI...
void DeleteSelected() override;
void DeleteEntityById(AZ::EntityId entityId) override;
void DeleteEntities(const EntityIdList& entities) override;
void DeleteEntityAndAllDescendants(AZ::EntityId entityId) override;
void DeleteEntitiesAndAllDescendants(const EntityIdList& entities) override;
private:
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
};
}
@@ -68,6 +68,8 @@
#include <AzToolsFramework/AssetEditor/AssetEditorBus.h>
#include <AzToolsFramework/Render/EditorIntersectorComponent.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiSystemComponent.h>
#include <AzToolsFramework/Undo/UndoCacheInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <QtWidgets/QMessageBox>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QFileInfo::d_ptr': class 'QSharedDataPointer<QFileInfoPrivate>' needs to have dll-interface to be used by clients of class 'QFileInfo'
@@ -78,7 +80,6 @@ AZ_POP_DISABLE_OVERRIDE_WARNING
#include <QJsonObject>
#include <QMap>
// Not possible to use AZCore's operator new overrides until we address the overall problems
// with allocators, or more likely convert AzToolsFramework to a DLL and restrict overloading to
// within the DLL. Since this is currently linked as a lib, overriding new and delete would require
@@ -95,9 +96,6 @@ namespace AzToolsFramework
static const char* s_startupLogWindow = "Startup";
static const char* s_prefabSystemKey = "/Amazon/Editor/Preferences/EnablePrefabSystem";
static const char* s_legacySlicesAssertKey = "/Amazon/Editor/Preferences/ShouldAssertForLegacySlicesUsage";
template<typename IdContainerType>
void DeleteEntities(const IdContainerType& entityIds)
{
@@ -341,8 +339,9 @@ namespace AzToolsFramework
, m_isInIsolationMode(false)
{
ToolsApplicationRequests::Bus::Handler::BusConnect();
m_engineConfigImpl.reset(new ToolsApplication::EngineConfigImpl(AzToolsFramework::Internal::s_startupLogWindow,
AzToolsFramework::Internal::s_engineConfigFileName));
m_engineConfigImpl.reset(new ToolsApplication::EngineConfigImpl(AzToolsFramework::Internal::s_startupLogWindow, AzToolsFramework::Internal::s_engineConfigFileName));
m_undoCache.RegisterToUndoCacheInterface();
}
ToolsApplication::~ToolsApplication()
@@ -393,6 +392,11 @@ namespace AzToolsFramework
{
Application::Start(descriptor, startupParameters);
InitializeEngineConfig();
m_editorEntityManager.Start();
m_editorEntityAPI = AZ::Interface<EditorEntityAPI>::Get();
AZ_Assert(m_editorEntityAPI, "ToolsApplication - Could not retrieve instance of EditorEntityAPI");
}
void ToolsApplication::InitializeEngineConfig()
@@ -416,7 +420,11 @@ namespace AzToolsFramework
{
FlushUndo();
m_undoCache.Clear();
auto undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
if (undoCacheInterface)
{
undoCacheInterface->Clear();
}
delete m_undoStack;
m_undoStack = nullptr;
@@ -426,6 +434,19 @@ namespace AzToolsFramework
m_highlightedEntities.set_capacity(0);
m_dirtyEntities = {};
bool isPrefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (isPrefabSystemEnabled)
{
// This resets the editor context thereby asking the systems that own the entities to destroy them. By doing this, we are
// duly giving the authority to delete the entities to the systems that owns them, rather than leaving it to the
// ComponentApplication to do the cleanup.
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequestBus::Events::ResetEditorContext);
}
GetSerializeContext()->DestroyEditContext();
Application::Stop();
@@ -554,7 +575,11 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
m_undoCache.PurgeCache(entity->GetId());
auto undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
if (undoCacheInterface)
{
undoCacheInterface->PurgeCache(entity->GetId());
}
MarkEntityDeselected(entity->GetId());
SetEntityHighlighted(entity->GetId(), false);
@@ -724,7 +749,6 @@ namespace AzToolsFramework
void ToolsApplication::SetEntityHighlighted(AZ::EntityId entityId, bool highlighted)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
AZ_Assert(entityId.IsValid(), "Attempting to mark an invalid entity as highlighted.");
auto foundIter = AZStd::find(m_highlightedEntities.begin(), m_highlightedEntities.end(), entityId);
if (foundIter != m_highlightedEntities.end())
@@ -893,16 +917,34 @@ namespace AzToolsFramework
void ToolsApplication::DeleteSelected()
{
if (IsPrefabSystemEnabled())
{
m_editorEntityAPI->DeleteSelected();
return;
}
Internal::DeleteEntities(m_selectedEntities);
}
void ToolsApplication::DeleteEntityAndAllDescendants(AZ::EntityId entityId)
{
if (IsPrefabSystemEnabled())
{
m_editorEntityAPI->DeleteEntityAndAllDescendants(entityId);
return;
}
DeleteEntitiesAndAllDescendants({ entityId });
}
void ToolsApplication::DeleteEntitiesAndAllDescendants(const EntityIdList& entities)
{
if (IsPrefabSystemEnabled())
{
m_editorEntityAPI->DeleteEntitiesAndAllDescendants(entities);
return;
}
const EntityIdSet entitiesAndDescendants = GatherEntitiesAndAllDescendents(entities);
Internal::DeleteEntities(entitiesAndDescendants);
}
@@ -1367,11 +1409,23 @@ namespace AzToolsFramework
void ToolsApplication::DeleteEntityById(AZ::EntityId entityId)
{
if (IsPrefabSystemEnabled())
{
m_editorEntityAPI->DeleteEntityById(entityId);
return;
}
DeleteEntities({ entityId });
}
void ToolsApplication::DeleteEntities(const EntityIdList& entities)
{
if (IsPrefabSystemEnabled())
{
m_editorEntityAPI->DeleteEntities(entities);
return;
}
Internal::DeleteEntities(entities);
}
@@ -1572,7 +1626,6 @@ namespace AzToolsFramework
else
{
// we're at the root
// only undo at bottom of scope (first invoked ScopedUndoBatch in
// chain/hierarchy must go out of scope)
CreateUndosForDirtyEntities();
@@ -1612,48 +1665,68 @@ namespace AzToolsFramework
return;
}
// If the current undo batch has commands in it, then we have to check that we do not add duplicates
// However if it starts out empty, we can just add things straight from the Set to the undo batch
bool mustCheckDuplicates = !m_currentBatchUndo->GetChildren().empty();
for (AZ::EntityId entityId : m_dirtyEntities)
if (!IsPrefabSystemEnabled())
{
AZ::Entity* entity = nullptr;
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, entityId);
// If the current undo batch has commands in it, then we have to check that we do not add duplicates
// However if it starts out empty, we can just add things straight from the Set to the undo batch
bool mustCheckDuplicates = !m_currentBatchUndo->GetChildren().empty();
if (entity)
for (AZ::EntityId entityId : m_dirtyEntities)
{
EntityStateCommand* state = nullptr;
AZ::Entity* entity = nullptr;
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, entityId);
if (mustCheckDuplicates)
if (entity)
{
// Check if this entity is already in the current undo batch
state = azdynamic_cast<EntityStateCommand*>(m_currentBatchUndo->Find(
static_cast<AZ::u64>(entityId), AZ::AzTypeInfo<EntityStateCommand>::Uuid()));
EntityStateCommand* state = nullptr;
if (mustCheckDuplicates)
{
// Check if this entity is already in the current undo batch
state = azdynamic_cast<EntityStateCommand*>(
m_currentBatchUndo->Find(static_cast<AZ::u64>(entityId), AZ::AzTypeInfo<EntityStateCommand>::Uuid()));
}
if (!state)
{
state = aznew EntityStateCommand(static_cast<AZ::u64>(entityId));
state->SetParent(m_currentBatchUndo);
// capture initial state of entity (before undo)
state->Capture(entity, true);
}
// capture last state of entity (after undo) - for redo
state->Capture(entity, false);
}
if (!state)
{
state = aznew EntityStateCommand(static_cast<AZ::u64>(entityId));
state->SetParent(m_currentBatchUndo);
// capture initial state of entity (before undo)
state->Capture(entity, true);
}
// capture last state of entity (after undo) - for redo
state->Capture(entity, false);
m_undoCache.UpdateCache(entityId);
}
}
else
{
auto prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get();
if (prefabPublicInterface)
{
// Compared to the preemptive undo cache, we can avoid the duplicate check.
// Multiple changes to the same entity are just split between different undo nodes.
for (AZ::EntityId entityId : m_dirtyEntities)
{
prefabPublicInterface->GenerateUndoNodesForEntityChangeAndUpdateCache(entityId, m_currentBatchUndo);
}
}
m_undoCache.UpdateCache(entityId);
}
}
void ToolsApplication::ConsistencyCheckUndoCache()
{
for (auto && entityEntry : m_entities)
auto undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
if (undoCacheInterface)
{
m_undoCache.Validate((entityEntry.second)->GetId());
for (auto&& entityEntry : m_entities)
{
undoCacheInterface->Validate((entityEntry.second)->GetId());
}
}
}
@@ -1769,26 +1842,6 @@ namespace AzToolsFramework
return m_engineConfigImpl->GetEngineVersion();
}
bool ToolsApplication::IsLegacySliceSystemEnabled() const
{
bool value = false;
if (auto* registry = AZ::SettingsRegistry::Get())
{
registry->Get(value, Internal::s_prefabSystemKey);
}
return !value;
}
bool ToolsApplication::ShouldAssertForLegacySlicesUsage() const
{
bool value = false;
if (auto* registry = AZ::SettingsRegistry::Get())
{
registry->Get(value, Internal::s_legacySlicesAssertKey);
}
return value;
}
void ToolsApplication::CreateAndAddEntityFromComponentTags(const AZStd::vector<AZ::Crc32>& requiredTags, const char* entityName)
{
if (!entityName || !entityName[0])
@@ -17,12 +17,19 @@
#include <AzFramework/Application/Application.h>
#include <AzFramework/Asset/SimpleAsset.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/API/EditorEntityAPI.h>
#include <AzToolsFramework/Application/EditorEntityManager.h>
#include <AzToolsFramework/Commands/PreemptiveUndoCache.h>
#pragma once
namespace AzToolsFramework
{
namespace UndoSystem
{
class UndoCacheInterface;
}
class ToolsApplication
: public AzFramework::Application
, public ToolsApplicationRequests::Bus::Handler
@@ -145,8 +152,6 @@ namespace AzToolsFramework
bool IsEditorInIsolationMode() override;
const char* GetEngineRootPath() const override;
const char* GetEngineVersion() const override;
bool IsLegacySliceSystemEnabled() const override;
bool ShouldAssertForLegacySlicesUsage() const override;
void CreateAndAddEntityFromComponentTags(const AZStd::vector<AZ::Crc32>& requiredTags, const char* entityName) override;
@@ -183,6 +188,12 @@ namespace AzToolsFramework
class EngineConfigImpl;
AZStd::unique_ptr<EngineConfigImpl> m_engineConfigImpl;
EditorEntityAPI* m_editorEntityAPI = nullptr;
EditorEntityManager m_editorEntityManager;
UndoSystem::UndoCacheInterface* m_undoCacheInterface = nullptr;
};
} // namespace AzToolsFramework
@@ -28,7 +28,7 @@
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/FolderThumbnail.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/SourceThumbnail.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/AssetBrowserProductThumbnail.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/ProductThumbnail.h>
#include <AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.h>
#include <AzToolsFramework/Slice/SliceUtilities.h>
@@ -17,7 +17,7 @@
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/Entries/ProductAssetBrowserEntry.h>
#include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/AssetBrowserProductThumbnail.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/ProductThumbnail.h>
#include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntryCache.h>
#include <QVariant>
@@ -120,13 +120,14 @@ namespace AzToolsFramework
void ProductAssetBrowserEntry::ThumbnailUpdated()
{
// if source is displaying product's thumbnail, then it needs to also listen to its ThumbnailUpdated
if (m_parentAssetEntry)
if (EntryCache* cache = EntryCache::GetInstance())
{
if (EntryCache* cache = EntryCache::GetInstance())
// if source is displaying product's thumbnail, then it needs to also listen to its ThumbnailUpdated
if (m_parentAssetEntry)
{
cache->m_dirtyThumbnailsSet.insert(m_parentAssetEntry);
}
cache->m_dirtyThumbnailsSet.insert(this);
}
}
@@ -207,6 +207,7 @@ namespace AzToolsFramework
auto source = azrtti_cast<SourceAssetBrowserEntry*>(itFile->second);
source->m_sourceId = sourceWithFileIdEntry.second.m_sourceID;
source->m_sourceUuid = sourceWithFileIdEntry.second.m_sourceGuid;
source->PathsUpdated(); // update thumbnailkey to valid uuid
EntryCache::GetInstance()->m_sourceUuidMap[source->m_sourceUuid] = source;
EntryCache::GetInstance()->m_sourceIdMap[source->m_sourceId] = source;
return true;
@@ -170,7 +170,7 @@ namespace AzToolsFramework
SharedThumbnailKey SourceAssetBrowserEntry::CreateThumbnailKey()
{
return MAKE_TKEY(SourceThumbnailKey, m_fullPath.c_str());
return MAKE_TKEY(SourceThumbnailKey, m_sourceUuid);
}
SharedThumbnailKey SourceAssetBrowserEntry::GetSourceControlThumbnailKey() const
@@ -14,7 +14,7 @@
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/AssetBrowserProductThumbnail.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/ProductThumbnail.h>
#include <QPixmap>
namespace AzToolsFramework
@@ -37,6 +37,21 @@ namespace AzToolsFramework
const AZ::Data::AssetType& ProductThumbnailKey::GetAssetType() const { return m_assetType; }
size_t ProductThumbnailKey::GetHash() const
{
return m_assetType.GetHash();
}
bool ProductThumbnailKey::Equals(const ThumbnailKey* other) const
{
if (!ThumbnailKey::Equals(other))
{
return false;
}
// products displayed in Asset Browser have icons based on asset type, so multiple different products with same asset type will have same thumbnail
return m_assetId == azrtti_cast<const ProductThumbnailKey*>(other)->GetAssetId();
}
//////////////////////////////////////////////////////////////////////////
// ProductThumbnail
//////////////////////////////////////////////////////////////////////////
@@ -93,7 +108,7 @@ namespace AzToolsFramework
// ProductThumbnailCache
//////////////////////////////////////////////////////////////////////////
ProductThumbnailCache::ProductThumbnailCache()
: ThumbnailCache<ProductThumbnail, ProductKeyHash, ProductKeyEqual>() {}
: ThumbnailCache<ProductThumbnail>() {}
ProductThumbnailCache::~ProductThumbnailCache() = default;
@@ -39,6 +39,14 @@ namespace AzToolsFramework
return m_isGem;
}
bool FolderThumbnailKey::Equals(const ThumbnailKey* other) const
{
if (!ThumbnailKey::Equals(other))
{
return false;
}
return m_isGem == azrtti_cast<const FolderThumbnailKey*>(other)->IsGem();
}
//////////////////////////////////////////////////////////////////////////
// FolderThumbnail
//////////////////////////////////////////////////////////////////////////
@@ -68,7 +76,7 @@ namespace AzToolsFramework
// FolderThumbnailCache
//////////////////////////////////////////////////////////////////////////
FolderThumbnailCache::FolderThumbnailCache()
: ThumbnailCache<FolderThumbnail, FolderKeyHash, FolderKeyEqual>() {}
: ThumbnailCache<FolderThumbnail>() {}
FolderThumbnailCache::~FolderThumbnailCache() = default;
@@ -33,6 +33,7 @@ namespace AzToolsFramework
FolderThumbnailKey(const char* folderPath, bool isGem);
const AZStd::string& GetFolderPath() const;
bool IsGem() const;
bool Equals(const ThumbnailKey* other) const override;
protected:
//! Absolute folder path
@@ -50,36 +51,10 @@ namespace AzToolsFramework
void LoadThread() override;
};
namespace
{
class FolderKeyHash
{
public:
size_t operator() (const SharedThumbnailKey& /*val*/) const
{
return 0;
}
};
class FolderKeyEqual
{
public:
bool operator()(const SharedThumbnailKey& val1, const SharedThumbnailKey& val2) const
{
auto folderThumbnailKey1 = azrtti_cast<const FolderThumbnailKey*>(val1.data());
auto folderThumbnailKey2 = azrtti_cast<const FolderThumbnailKey*>(val2.data());
if (!folderThumbnailKey1 || !folderThumbnailKey2)
{
return false;
}
// There are only two thumbnails in this cache, one for gem icon and one for folder icon
return folderThumbnailKey1->IsGem() == folderThumbnailKey2->IsGem();
}
};
}
//! FolderAssetBrowserEntry thumbnails
class FolderThumbnailCache
: public ThumbnailCache<FolderThumbnail, FolderKeyHash, FolderKeyEqual>
: public ThumbnailCache<FolderThumbnail>
{
public:
FolderThumbnailCache();
@@ -0,0 +1,128 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Asset/AssetTypeInfoBus.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/ProductThumbnail.h>
#include <QPixmap>
namespace AzToolsFramework
{
namespace AssetBrowser
{
//////////////////////////////////////////////////////////////////////////
// ProductThumbnailKey
//////////////////////////////////////////////////////////////////////////
ProductThumbnailKey::ProductThumbnailKey(const AZ::Data::AssetId& assetId)
: ThumbnailKey()
, m_assetId(assetId)
{
AZ::Data::AssetInfo info;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(info, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, m_assetId);
m_assetType = info.m_assetType;
}
const AZ::Data::AssetId& ProductThumbnailKey::GetAssetId() const { return m_assetId; }
const AZ::Data::AssetType& ProductThumbnailKey::GetAssetType() const { return m_assetType; }
size_t ProductThumbnailKey::GetHash() const
{
return m_assetType.GetHash();
}
bool ProductThumbnailKey::Equals(const ThumbnailKey* other) const
{
if (!ThumbnailKey::Equals(other))
{
return false;
}
// products displayed in Asset Browser have icons based on asset type, so multiple different products with same asset type will have same thumbnail
return m_assetId == azrtti_cast<const ProductThumbnailKey*>(other)->GetAssetId();
}
//////////////////////////////////////////////////////////////////////////
// ProductThumbnail
//////////////////////////////////////////////////////////////////////////
static const char* DEFAULT_PRODUCT_ICON_PATH = "Editor/Icons/AssetBrowser/DefaultProduct_16.svg";
ProductThumbnail::ProductThumbnail(Thumbnailer::SharedThumbnailKey key, int thumbnailSize)
: Thumbnail(key, thumbnailSize)
{}
void ProductThumbnail::LoadThread()
{
auto productKey = azrtti_cast<const ProductThumbnailKey*>(m_key.data());
AZ_Assert(productKey, "Incorrect key type, excpected ProductThumbnailKey");
QString iconPath;
AZ::AssetTypeInfoBus::EventResult(iconPath, productKey->GetAssetType(), &AZ::AssetTypeInfo::GetBrowserIcon);
if (!iconPath.isEmpty())
{
// is it an embedded resource or absolute path?
bool isUsablePath = (iconPath.startsWith(":") || (!AzFramework::StringFunc::Path::IsRelative(iconPath.toUtf8().constData())));
if (!isUsablePath)
{
// getting here means it needs resolution. Can we find the real path of the file? This also searches in gems for sources.
bool foundIt = false;
AZStd::string watchFolder;
AZ::Data::AssetInfo assetInfo;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(foundIt, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, iconPath.toUtf8().constData(), assetInfo, watchFolder);
if (foundIt)
{
// the absolute path is join(watchfolder, relativepath); // since its relative to the watch folder.
AZStd::string finalPath;
AzFramework::StringFunc::Path::Join(watchFolder.c_str(), assetInfo.m_relativePath.c_str(), finalPath);
iconPath = QString::fromUtf8(finalPath.c_str());
}
}
}
else
{
// no pixmap specified - use default.
iconPath = QString::fromUtf8(DEFAULT_PRODUCT_ICON_PATH);
}
m_icon = QIcon(iconPath);
if (m_icon.isNull())
{
m_state = State::Failed;
}
}
//////////////////////////////////////////////////////////////////////////
// ProductThumbnailCache
//////////////////////////////////////////////////////////////////////////
ProductThumbnailCache::ProductThumbnailCache()
: ThumbnailCache<ProductThumbnail>() {}
ProductThumbnailCache::~ProductThumbnailCache() = default;
const char* ProductThumbnailCache::GetProviderName() const
{
return ProviderName;
}
bool ProductThumbnailCache::IsSupportedThumbnail(Thumbnailer::SharedThumbnailKey key) const
{
return azrtti_istypeof<const ProductThumbnailKey*>(key.data());
}
} // namespace AssetBrowser
} // namespace AzToolsFramework
#include "AssetBrowser/Thumbnails/moc_ProductThumbnail.cpp"
@@ -31,6 +31,8 @@ namespace AzToolsFramework
explicit ProductThumbnailKey(const AZ::Data::AssetId& assetId);
const AZ::Data::AssetId& GetAssetId() const;
const AZ::Data::AssetType& GetAssetType() const;
size_t GetHash() const override;
bool Equals(const ThumbnailKey* other) const override;
protected:
AZ::Data::AssetId m_assetId;
@@ -48,42 +50,9 @@ namespace AzToolsFramework
void LoadThread() override;
};
namespace
{
class ProductKeyHash
{
public:
size_t operator() (const Thumbnailer::SharedThumbnailKey& val) const
{
auto productThumbnailKey = azrtti_cast<const ProductThumbnailKey*>(val.data());
if (!productThumbnailKey)
{
return 0;
}
return productThumbnailKey->GetAssetType().GetHash();
}
};
class ProductKeyEqual
{
public:
bool operator()(const Thumbnailer::SharedThumbnailKey& val1, const Thumbnailer::SharedThumbnailKey& val2) const
{
auto productThumbnailKey1 = azrtti_cast<const ProductThumbnailKey*>(val1.data());
auto productThumbnailKey2 = azrtti_cast<const ProductThumbnailKey*>(val2.data());
if (!productThumbnailKey1 || !productThumbnailKey2)
{
return false;
}
// products displayed in Asset Browser have icons based on asset type, so multiple different products with same asset type will have same thumbnail
return productThumbnailKey1->GetAssetType() == productThumbnailKey2->GetAssetType();
}
};
}
//! ProductAssetBrowserEntry thumbnails
class ProductThumbnailCache
: public Thumbnailer::ThumbnailCache<ProductThumbnail, ProductKeyHash, ProductKeyEqual>
: public Thumbnailer::ThumbnailCache<ProductThumbnail>
{
public:
ProductThumbnailCache();
@@ -11,6 +11,7 @@
*/
#include <AzCore/EBus/Results.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
@@ -26,21 +27,29 @@ namespace AzToolsFramework
//////////////////////////////////////////////////////////////////////////
// SourceThumbnailKey
//////////////////////////////////////////////////////////////////////////
SourceThumbnailKey::SourceThumbnailKey(const char* fileName)
SourceThumbnailKey::SourceThumbnailKey(const AZ::Uuid& sourceUuid)
: ThumbnailKey()
, m_fileName(fileName)
, m_sourceUuid(sourceUuid)
{
AzFramework::StringFunc::Path::Split(fileName, nullptr, nullptr, nullptr, &m_extension);
}
const AZStd::string& SourceThumbnailKey::GetFileName() const
const AZ::Uuid& SourceThumbnailKey::GetSourceUuid() const
{
return m_fileName;
return m_sourceUuid;
}
const AZStd::string& SourceThumbnailKey::GetExtension() const
size_t SourceThumbnailKey::GetHash() const
{
return m_extension;
return m_sourceUuid.GetHash();
}
bool SourceThumbnailKey::Equals(const ThumbnailKey* other) const
{
if (!ThumbnailKey::Equals(other))
{
return false;
}
return m_sourceUuid == azrtti_cast<const SourceThumbnailKey*>(other)->GetSourceUuid();
}
//////////////////////////////////////////////////////////////////////////
@@ -59,48 +68,54 @@ namespace AzToolsFramework
auto sourceKey = azrtti_cast<const SourceThumbnailKey*>(m_key.data());
AZ_Assert(sourceKey, "Incorrect key type, excpected SourceThumbnailKey");
// note that there might not actually be a UUID for this yet. So we don't look it up.
AZ::EBusAggregateResults<SourceFileDetails> results;
AssetBrowserInteractionNotificationBus::BroadcastResult(results, &AssetBrowserInteractionNotificationBus::Events::GetSourceFileDetails, sourceKey->GetFileName().c_str());
// there could be multiple listeners to this, return the first one with actual image path
auto it = AZStd::find_if(results.values.begin(), results.values.end(), [](const SourceFileDetails& details) { return !details.m_sourceThumbnailPath.empty(); });
bool foundIt = false;
AZStd::string watchFolder;
AZ::Data::AssetInfo assetInfo;
AssetSystemRequestBus::BroadcastResult(foundIt, &AssetSystemRequestBus::Events::GetSourceInfoBySourceUUID, sourceKey->GetSourceUuid(), assetInfo, watchFolder);
QString iconPathToUse;
if (it != results.values.end())
if (foundIt)
{
const char* resultPath = it->m_sourceThumbnailPath.c_str();
// its an ordered bus, though, so first one wins.
// we have to massage this though. there are three valid possibilities
// 1. its a relative path to source, in which case we have to find the full path
// 2. its an absolute path, in which case we use it as-is
// 3. its an embedded resource, in which case we use it as is.
// note that there might not actually be a UUID for this yet. So we don't look it up.
AZ::EBusAggregateResults<SourceFileDetails> results;
AssetBrowserInteractionNotificationBus::BroadcastResult(results, &AssetBrowserInteractionNotificationBus::Events::GetSourceFileDetails, assetInfo.m_relativePath.c_str());
// there could be multiple listeners to this, return the first one with actual image path
auto it = AZStd::find_if(results.values.begin(), results.values.end(), [](const SourceFileDetails& details) { return !details.m_sourceThumbnailPath.empty(); });
// is it an embedded resource or absolute path?
if ((resultPath[0] == ':')||(!AzFramework::StringFunc::Path::IsRelative(resultPath)))
if (it != results.values.end())
{
iconPathToUse = QString::fromUtf8(resultPath);
}
else
{
// getting here means its a relative path. Can we find the real path of the file? This also searches in gems for sources.
bool foundIt = false;
AZStd::string watchFolder;
AZ::Data::AssetInfo assetInfo;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(foundIt, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, resultPath, assetInfo, watchFolder);
const char* resultPath = it->m_sourceThumbnailPath.c_str();
// its an ordered bus, though, so first one wins.
// we have to massage this though. there are three valid possibilities
// 1. its a relative path to source, in which case we have to find the full path
// 2. its an absolute path, in which case we use it as-is
// 3. its an embedded resource, in which case we use it as is.
AZ_WarningOnce("Asset Browser", foundIt, "Unable to find source icon file in any source folders or gems: %s\n", resultPath);
if (foundIt)
// is it an embedded resource or absolute path?
if ((resultPath[0] == ':') || (!AzFramework::StringFunc::Path::IsRelative(resultPath)))
{
// the absolute path is join(watchfolder, relativepath); // since its relative to the watch folder.
AZStd::string finalPath;
AzFramework::StringFunc::Path::Join(watchFolder.c_str(), assetInfo.m_relativePath.c_str(), finalPath);
iconPathToUse = QString::fromUtf8(finalPath.c_str());
iconPathToUse = QString::fromUtf8(resultPath);
}
else
{
// getting here means its a relative path. Can we find the real path of the file? This also searches in gems for sources.
foundIt = false;
AssetSystemRequestBus::BroadcastResult(foundIt, &AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, resultPath, assetInfo, watchFolder);
AZ_WarningOnce("Asset Browser", foundIt, "Unable to find source icon file in any source folders or gems: %s\n", resultPath);
if (foundIt)
{
// the absolute path is join(watchfolder, relativepath); // since its relative to the watch folder.
AZStd::string finalPath;
AzFramework::StringFunc::Path::Join(watchFolder.c_str(), assetInfo.m_relativePath.c_str(), finalPath);
iconPathToUse = QString::fromUtf8(finalPath.c_str());
}
}
}
}
else
if (iconPathToUse.isEmpty())
{
const char* engineRoot = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
@@ -116,7 +131,7 @@ namespace AzToolsFramework
// SourceThumbnailCache
//////////////////////////////////////////////////////////////////////////
SourceThumbnailCache::SourceThumbnailCache()
: ThumbnailCache<SourceThumbnail, SourceKeyHash, SourceKeyEqual>() {}
: ThumbnailCache<SourceThumbnail>() {}
SourceThumbnailCache::~SourceThumbnailCache() = default;
@@ -12,7 +12,6 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/std/string/string.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <QMutex>
#endif
@@ -31,15 +30,13 @@ namespace AzToolsFramework
public:
AZ_RTTI(SourceThumbnailKey, "{4AF3F33A-4B16-491D-93A5-0CC96DC59814}", ThumbnailKey);
explicit SourceThumbnailKey(const char* fileName);
const AZStd::string& GetFileName() const;
const AZStd::string& GetExtension() const;
explicit SourceThumbnailKey(const AZ::Uuid& sourceUuid);
const AZ::Uuid& GetSourceUuid() const;
size_t GetHash() const override;
bool Equals(const ThumbnailKey* other) const override;
protected:
//! absolute path
AZStd::string m_fileName;
//! file extension
AZStd::string m_extension;
AZ::Uuid m_sourceUuid;
};
class SourceThumbnail
@@ -56,41 +53,9 @@ namespace AzToolsFramework
static QMutex m_mutex;
};
namespace
{
class SourceKeyHash
{
public:
size_t operator() (const SharedThumbnailKey& val) const
{
auto sourceThumbnailKey = azrtti_cast<const SourceThumbnailKey*>(val.data());
if (!sourceThumbnailKey)
{
return 0;
}
return AZStd::hash<AZStd::string>()(sourceThumbnailKey->GetFileName());
}
};
class SourceKeyEqual
{
public:
bool operator()(const SharedThumbnailKey& val1, const SharedThumbnailKey& val2) const
{
auto sourceThumbnailKey1 = azrtti_cast<const SourceThumbnailKey*>(val1.data());
auto sourceThumbnailKey2 = azrtti_cast<const SourceThumbnailKey*>(val2.data());
if (!sourceThumbnailKey1 || !sourceThumbnailKey2)
{
return false;
}
return sourceThumbnailKey1->GetFileName() == sourceThumbnailKey2->GetFileName();
}
};
}
//! SourceAssetBrowserEntry thumbnails
class SourceThumbnailCache
: public ThumbnailCache<SourceThumbnail, SourceKeyHash, SourceKeyEqual>
: public ThumbnailCache<SourceThumbnail>
{
public:
SourceThumbnailCache();
@@ -307,6 +307,10 @@ namespace AzToolsFramework
}
}
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
AZ::u64 fileSize = 0;
@@ -323,7 +327,7 @@ namespace AzToolsFramework
AZ_Warning(logWindowName, false, "File (%s) size (%d) is bigger than the max bundle size (%d).\n", assetFileInfo.m_assetRelativePath.c_str(), fileSize, maxSizeInBytes);
}
if (AzFramework::StringFunc::EndsWith(assetFileInfo.m_assetRelativePath, "level.pak"))
if (!usePrefabSystemForLevels && (AzFramework::StringFunc::EndsWith(assetFileInfo.m_assetRelativePath, "level.pak")))
{
AZStd::string levelFolder;
AzFramework::StringFunc::Path::GetFolderPath(assetFileInfo.m_assetRelativePath.c_str(), levelFolder);
@@ -31,6 +31,7 @@
#include <AzToolsFramework/Slice/SliceDependencyBrowserComponent.h>
#include <AzToolsFramework/Slice/SliceMetadataEntityContextComponent.h>
#include <AzToolsFramework/Slice/SliceRequestComponent.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponent.h>
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
#include <AzToolsFramework/ToolsComponents/AzToolsFrameworkConfigurationSystemComponent.h>
@@ -73,6 +74,7 @@ namespace AzToolsFramework
SliceMetadataEntityContextComponent::CreateDescriptor(),
SliceRequestComponent::CreateDescriptor(),
Prefab::PrefabSystemComponent::CreateDescriptor(),
Prefab::EditorPrefabComponent::CreateDescriptor(),
Components::EditorEntityActionComponent::CreateDescriptor(),
Components::EditorEntityIconComponent::CreateDescriptor(),
Components::EditorInspectorComponent::CreateDescriptor(),
@@ -16,7 +16,7 @@
#include "EntityTransformCommand.h"
#include <HexEdFramework/FrameworkCore/SelectionMessages.h>
#include <HexEd/WorldEditor/ToolsComponents/TransformComponentBus.h>
#include "PreemptiveUndoCache.h"
#include <AzToolsFramework/Undo/UndoCacheInterface.h>
namespace AzToolsFramework
{
@@ -31,6 +31,9 @@ namespace AzToolsFramework
m_priorTransforms[*it] = current;
m_nextTransforms[*it] = current;
}
m_undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
AZ_Assert(m_undoCacheInterface, "Could not get UndoCacheInterface on TransformCommand construction.");
}
void TransformCommand::Post()
@@ -46,7 +49,7 @@ namespace AzToolsFramework
for (auto it = m_priorTransforms.begin(); it != m_priorTransforms.end(); ++it)
{
PreemptiveUndoCache::Get()->UpdateCache(it->first);
m_undoCacheInterface->UpdateCache(it->first);
}
}
@@ -55,7 +58,7 @@ namespace AzToolsFramework
for (auto it = m_priorTransforms.begin(); it != m_priorTransforms.end(); ++it)
{
EBUS_EVENT_ID(it->first, TransformComponentMessages::Bus, SetLocalSRT, it->second);
PreemptiveUndoCache::Get()->UpdateCache(it->first);
m_undoCacheInterface->UpdateCache(it->first);
}
}
@@ -64,7 +67,7 @@ namespace AzToolsFramework
for (auto it = m_nextTransforms.begin(); it != m_nextTransforms.end(); ++it)
{
EBUS_EVENT_ID(it->first, TransformComponentMessages::Bus, SetLocalSRT, it->second);
PreemptiveUndoCache::Get()->UpdateCache(it->first);
m_undoCacheInterface->UpdateCache(it->first);
}
}
@@ -87,4 +90,4 @@ namespace AzToolsFramework
}
}
#endif
#endif
@@ -25,6 +25,11 @@
namespace AzToolsFramework
{
namespace UndoSystem
{
class UndoCacheInterface;
}
typedef AZStd::vector<AZ::EntityId> EntityList;
// transform command specializes undo to just care about the transform of an entity instead of the entire thing, for performance.
@@ -54,9 +59,12 @@ namespace AzToolsFramework
CapturedTransforms m_priorTransforms;
CapturedTransforms m_nextTransforms;
private:
UndoCacheInterface* m_undoCacheInterface;
};
}
#endif // disabled
#endif
#endif
@@ -53,6 +53,11 @@ namespace AzToolsFramework
s_cache->m_instance = this;
}
void PreemptiveUndoCache::RegisterToUndoCacheInterface()
{
AZ::Interface<UndoSystem::UndoCacheInterface>::Register(this);
}
PreemptiveUndoCache::~PreemptiveUndoCache()
{
if (s_cache)
@@ -60,6 +65,11 @@ namespace AzToolsFramework
s_cache->m_instance = nullptr;
s_cache.Reset();
}
if (AZ::Interface<UndoSystem::UndoCacheInterface>::Get() == this)
{
AZ::Interface<UndoSystem::UndoCacheInterface>::Unregister(this);
}
}
PreemptiveUndoCache* PreemptiveUndoCache::Get()
@@ -173,4 +183,4 @@ namespace AzToolsFramework
return it->second;
}
} // namespace AzToolsFramework
} // namespace AzToolsFramework
@@ -18,6 +18,8 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzToolsFramework/Undo/UndoCacheInterface.h>
// Enable to generate warnings for entity data changes not caught by undo batches.
#if defined(AZ_DEBUG_BUILD) && !defined(ENABLE_UNDOCACHE_CONSISTENCY_CHECKS)
# define ENABLE_UNDOCACHE_CONSISTENCY_CHECKS
@@ -29,6 +31,7 @@ namespace AzToolsFramework
// so that the user does not have to inform us before they make a change, only after they make a change
// it also allows us to detect errors with change notification and not have to do multiple snapshots (before and after)
class PreemptiveUndoCache
: UndoSystem::UndoCacheInterface
{
public:
AZ_CLASS_ALLOCATOR(PreemptiveUndoCache, AZ::SystemAllocator, 0);
@@ -37,23 +40,21 @@ namespace AzToolsFramework
PreemptiveUndoCache();
~PreemptiveUndoCache();
void RegisterToUndoCacheInterface();
// UndoCacheInterface...
void UpdateCache(const AZ::EntityId& entityId) override;
void PurgeCache(const AZ::EntityId& entityId) override;
void Clear() override;
void Validate(const AZ::EntityId& entityId) override;
// whenever an entity appears in the world, as an atomic operation (ie, after all of its loading is complete, and the entity is active)
// we snapshot it here. then, whenever an entity changes, we have what it used to be.
typedef AZStd::vector<AZ::u8> CacheLineType;
void Validate(const AZ::EntityId& entityId);
// Store the new entity state or replace the old state
void UpdateCache(const AZ::EntityId& entityId);
// remove the cache line for the entity, if there is one
void PurgeCache(const AZ::EntityId& entityId);
// retrieve the last known state for an entity
const CacheLineType& Retrieve(const AZ::EntityId& entityId);
// clear the entire cache:
void Clear();
protected:
typedef AZStd::unordered_map<AZ::EntityId, CacheLineType> EntityStateMap;
@@ -55,6 +55,7 @@
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Undo/UndoCacheInterface.h>
namespace AzToolsFramework
@@ -163,10 +164,10 @@ namespace AzToolsFramework
//=========================================================================
void EditorEntityContextComponent::Activate()
{
m_isLegacySliceService = true;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(m_isLegacySliceService,
&AzToolsFramework::ToolsApplicationRequests::IsLegacySliceSystemEnabled);
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(prefabSystemEnabled,
&AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
m_isLegacySliceService = !prefabSystemEnabled;
if (m_isLegacySliceService)
{
@@ -190,6 +191,7 @@ namespace AzToolsFramework
EditorLegacyGameModeNotificationBus::Handler::BusConnect();
m_entityVisibilityBoundsUnionSystem.Connect();
}
//=========================================================================
@@ -449,7 +451,8 @@ namespace AzToolsFramework
}
else
{
AZ_Assert(!m_entityOwnershipService->m_shouldAssertForLegacySlicesUsage, "Not implemented");
loadedSuccessfully = static_cast<PrefabEditorEntityOwnershipService*>(m_entityOwnershipService.get())->LoadFromStream(
stream, AZStd::string_view(levelPakFile.toUtf8(), levelPakFile.size()) );
}
LoadFromStreamComplete(loadedSuccessfully);
@@ -495,7 +498,10 @@ namespace AzToolsFramework
}
else
{
AZ_Assert(!m_entityOwnershipService->m_shouldAssertForLegacySlicesUsage, "Not implemented");
auto* service = AZ::Interface<PrefabEditorEntityOwnershipInterface>::Get();
AZ_Assert(service, "Start play in editor could not start because there was no implementation for "
"PrefabEditorEntityOwnershipInterface");
service->StartPlayInEditor();
}
m_isRunningGame = true;
@@ -523,7 +529,10 @@ namespace AzToolsFramework
}
else
{
AZ_Assert(!m_entityOwnershipService->m_shouldAssertForLegacySlicesUsage, "Not implemented");
auto* service = AZ::Interface<PrefabEditorEntityOwnershipInterface>::Get();
AZ_Assert(service, "Stop play in editor could not complete because there was no implementation for "
"PrefabEditorEntityOwnershipInterface");
service->StopPlayInEditor();
}
ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, m_selectedBeforeStartingGame);
@@ -663,10 +672,6 @@ namespace AzToolsFramework
// need to be associated with the root metadata info component.
editorEntityOwnershipService->AssociateToRootMetadataEntity(entities);
}
else
{
AZ_Assert(!m_entityOwnershipService->m_shouldAssertForLegacySlicesUsage, "Not implemented");
}
SetupEditorEntities(entities);
}
@@ -742,6 +747,11 @@ namespace AzToolsFramework
}
}
if (m_undoCacheInterface == nullptr)
{
m_undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
}
// After activating all the entities, refresh their entries in the undo cache.
// We need to wait until after all the activations are complete. Otherwise, it's possible that the data for an entity
// will change based on other activations. For example, if we activate a child entity before its parent, the Transform
@@ -749,7 +759,7 @@ namespace AzToolsFramework
// out of sync with the child data.
for (AZ::Entity* entity : entities)
{
PreemptiveUndoCache::Get()->UpdateCache(entity->GetId());
m_undoCacheInterface->UpdateCache(entity->GetId());
}
}
@@ -41,6 +41,11 @@ namespace AzFramework
namespace AzToolsFramework
{
namespace UndoSystem
{
class UndoCacheInterface;
}
/**
* System component responsible for owning the edit-time entity context.
*
@@ -186,6 +191,8 @@ namespace AzToolsFramework
//! Edit time visibility management integrating entities with the IVisibilitySystem.
AzFramework::EntityVisibilityBoundsUnionSystem m_entityVisibilityBoundsUnionSystem;
bool m_isLegacySliceService;
UndoSystem::UndoCacheInterface* m_undoCacheInterface = nullptr;
};
} // namespace AzToolsFramework
@@ -22,6 +22,7 @@
#include <AzCore/std/sort.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzToolsFramework/API/ComponentEntityObjectBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
@@ -116,6 +117,8 @@ namespace AzToolsFramework
{
EditorEntityModel::EditorEntityModel()
{
AzFramework::ApplicationRequests::Bus::BroadcastResult(m_isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
EntityCompositionNotificationBus::Handler::BusConnect();
EditorOnlyEntityComponentNotificationBus::Handler::BusConnect();
EditorEntityRuntimeActivationChangeNotificationBus::Handler::BusConnect();
@@ -562,7 +565,8 @@ namespace AzToolsFramework
{
//retrieve or add an entity entry to the table
//the entry must exist, even if not connected, so children and other data can be assigned
auto& entityInfo = m_entityInfoTable[entityId];
[[maybe_unused]] auto [it, inserted] = m_entityInfoTable.try_emplace(entityId, m_isPrefabEnabled);
auto& entityInfo = it->second;
//the entity id defaults to invalid and must be set to match the requested id
//disconnect and reassign if there's a mismatch
@@ -878,7 +882,8 @@ namespace AzToolsFramework
}
}
EditorEntityModel::EditorEntityModelEntry::EditorEntityModelEntry()
EditorEntityModel::EditorEntityModelEntry::EditorEntityModelEntry(bool isPrefabEnabled)
: m_isPrefabEnabled(isPrefabEnabled)
{
}
@@ -1208,13 +1213,28 @@ namespace AzToolsFramework
auto childItr = m_childIndexCache.find(childId);
if (childItr != m_childIndexCache.end())
{
m_children.erase(m_children.begin() + childItr->second);
//rebuild index cache for faster lookup
m_childIndexCache.clear();
for (auto childIdToCache : m_children)
if (m_isPrefabEnabled)
{
m_childIndexCache[childIdToCache] = static_cast<AZ::u64>(m_childIndexCache.size());
// Take the last entry and move it into the removed spot instead of deleting the entry and having to move all
// following entries one step down.
AZ::EntityId backEntity = m_children.back();
m_children[childItr->second] = backEntity;
// Update cached index for the moved id to the new index.
m_childIndexCache[backEntity] = childItr->second;
// Now remove the deleted id from the children and cache.
m_childIndexCache.erase(childId);
m_children.erase(m_children.end() - 1);
}
else
{
m_children.erase(m_children.begin() + childItr->second);
// rebuild index cache for faster lookup
m_childIndexCache.clear();
for (auto childIdToCache : m_children)
{
m_childIndexCache[childIdToCache] = static_cast<AZ::u64>(m_childIndexCache.size());
}
}
}
}
@@ -171,7 +171,7 @@ namespace AzToolsFramework
, public PropertyEditorEntityChangeNotificationBus::Handler
{
public:
EditorEntityModelEntry();
explicit EditorEntityModelEntry(bool isPrefabEnabled);
~EditorEntityModelEntry();
// Separately connect to EditorEntityInfoRequestBus and refresh Entity
@@ -336,6 +336,7 @@ namespace AzToolsFramework
bool m_visible = true;
bool m_locked = false;
bool m_connected = false;
bool m_isPrefabEnabled = false;
AZStd::string m_name;
AZStd::string m_sliceAssetName;
AZStd::unordered_map<AZ::EntityId, AZ::u64> m_childIndexCache;
@@ -374,5 +375,6 @@ namespace AzToolsFramework
AZ::EntityId m_postInstantiateBeforeEntity;
AZ::EntityId m_postInstantiateSliceParent;
bool m_gotInstantiateSliceDetails = false;
bool m_isPrefabEnabled = false;
};
}
@@ -13,6 +13,7 @@
#pragma once
#include <AzCore/Component/Entity.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string.h>
@@ -20,6 +21,7 @@
namespace AzToolsFramework
{
class PrefabEditorEntityOwnershipInterface
{
public:
@@ -33,7 +35,13 @@ namespace AzToolsFramework
//! /return The optional reference to the prefab created.
virtual Prefab::InstanceOptionalReference CreatePrefab(
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Prefab::Instance>>&& nestedPrefabInstances,
const AZStd::string& filePath, Prefab::Instance& instanceToParentUnder) = 0;
AZ::IO::PathView filePath, Prefab::InstanceOptionalReference instanceToParentUnder = AZStd::nullopt) = 0;
virtual Prefab::InstanceOptionalReference GetRootPrefabInstance() = 0;
virtual bool LoadFromStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0;
virtual bool SaveToStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0;
virtual void StartPlayInEditor() = 0;
virtual void StopPlayInEditor() = 0;
};
}
@@ -11,14 +11,17 @@
*/
#include <AzCore/Component/Entity.h>
#include <AzCore/Script/ScriptSystemBus.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Spawnable/RootSpawnableInterface.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h>
#include <AzToolsFramework/Prefab/PrefabLoader.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/PrefabLoader.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
namespace AzToolsFramework
{
PrefabEditorEntityOwnershipService::PrefabEditorEntityOwnershipService(const AzFramework::EntityContextId& entityContextId,
@@ -26,8 +29,8 @@ namespace AzToolsFramework
: m_entityContextId(entityContextId)
, m_serializeContext(serializeContext)
{
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
m_shouldAssertForLegacySlicesUsage, &AzToolsFramework::ToolsApplicationRequests::ShouldAssertForLegacySlicesUsage);
AzFramework::ApplicationRequests::Bus::BroadcastResult(
m_shouldAssertForLegacySlicesUsage, &AzFramework::ApplicationRequests::ShouldAssertForLegacySlicesUsage);
AZ::Interface<PrefabEditorEntityOwnershipInterface>::Register(this);
}
@@ -46,7 +49,7 @@ namespace AzToolsFramework
AZ_Assert(m_loaderInterface != nullptr,
"Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work");
m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "/"));
m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "NewLevel.prefab"));
m_sliceOwnershipService.BusConnect(m_entityContextId);
m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage;
@@ -61,14 +64,33 @@ namespace AzToolsFramework
void PrefabEditorEntityOwnershipService::Destroy()
{
StopPlayInEditor();
m_editorSliceOwnershipService.BusDisconnect();
m_sliceOwnershipService.BusDisconnect();
m_rootInstance.reset();
if (m_rootInstance != nullptr)
{
// Need to save off the template id to remove the template after the instance is deleted.
Prefab::TemplateId templateId = m_rootInstance->GetTemplateId();
m_rootInstance.reset();
if (templateId != Prefab::InvalidTemplateId)
{
// Remove the template here so that if we're in a Deactivate/Activate cycle, it can recreate the template/rootInstance
// correctly
m_prefabSystemComponent->RemoveTemplate(templateId);
}
}
}
void PrefabEditorEntityOwnershipService::Reset()
{
// TBD
Prefab::TemplateId templateId = m_rootInstance->GetTemplateId();
if (templateId != Prefab::InvalidTemplateId)
{
m_rootInstance->SetTemplateId(Prefab::InvalidTemplateId);
m_prefabSystemComponent->RemoveTemplate(templateId);
}
m_rootInstance->Reset();
}
void PrefabEditorEntityOwnershipService::AddEntity(AZ::Entity* entity)
@@ -97,15 +119,8 @@ namespace AzToolsFramework
{
AZ_Assert(IsInitialized(), "Tried to destroy an entity without initializing the Entity Ownership Service");
AZ_Assert(m_entitiesRemovedCallback, "Callback function for DestroyEntityById has not been set.");
AZStd::unique_ptr<AZ::Entity> detachedEntity = m_rootInstance->DetachEntity(entityId);
if (detachedEntity)
{
AzFramework::SliceEntityRequestBus::MultiHandler::BusDisconnect(detachedEntity->GetId());
m_entitiesRemovedCallback({ entityId });
return true;
}
return false;
// The detached entity now gets deleted because the unique_ptr gets out of scope
OnEntityRemoved(entityId);
return true;
}
void PrefabEditorEntityOwnershipService::GetNonPrefabEntities(EntityList& entities)
@@ -133,25 +148,121 @@ namespace AzToolsFramework
m_entitiesAddedCallback(entities);
}
bool PrefabEditorEntityOwnershipService::LoadFromStream(AZ::IO::GenericStream& /*stream*/, bool /*remapIds*/,
EntityIdToEntityIdMap* /*idRemapTable*/, const AZ::ObjectStream::FilterDescriptor& /*filterDesc*/)
bool PrefabEditorEntityOwnershipService::LoadFromStream(AZ::IO::GenericStream& stream, AZStd::string_view filename)
{
Reset();
// Make loading from stream to behave the same in terms of filesize as regular loading of prefabs
// This may need to be revisited in the future for supporting higher sizes along with prefab loading
if (stream.GetLength() > Prefab::MaxPrefabFileSize)
{
AZ_Error("Prefab", false, "'%.*s' prefab content is bigger than the max supported size (%f MB)", AZ_STRING_ARG(filename), Prefab::MaxPrefabFileSize / (1024.f * 1024.f));
return false;
}
const size_t bufSize = stream.GetLength();
AZStd::unique_ptr<char[]> buf(new char[bufSize]);
AZ::IO::SizeType bytes = stream.Read(bufSize, buf.get());
Prefab::TemplateId templateId = m_loaderInterface->LoadTemplateFromString(AZStd::string_view(buf.get(), bytes), filename);
if (templateId == Prefab::InvalidTemplateId)
{
AZ_Error("Prefab", false, "Couldn't load prefab content from '%.*s'", AZ_STRING_ARG(filename));
return false;
}
m_rootInstance->SetTemplateId(templateId);
m_rootInstance->SetTemplateSourcePath(m_loaderInterface->GetRelativePathToProject(filename));
m_prefabSystemComponent->PropagateTemplateChanges(templateId);
return true;
}
bool PrefabEditorEntityOwnershipService::LoadFromStream(
[[maybe_unused]] AZ::IO::GenericStream& stream,
[[maybe_unused]] bool remapIds,
[[maybe_unused]] EntityIdToEntityIdMap* idRemapTable,
[[maybe_unused]] const AZ::ObjectStream::FilterDescriptor& filterDesc)
{
return false;
}
bool PrefabEditorEntityOwnershipService::SaveToStream(AZ::IO::GenericStream& stream, AZStd::string_view filename)
{
AZ::IO::Path relativePath = m_loaderInterface->GetRelativePathToProject(filename);
AzToolsFramework::Prefab::TemplateId templateId = m_prefabSystemComponent->GetTemplateIdFromFilePath(relativePath);
m_rootInstance->SetTemplateSourcePath(relativePath);
if (templateId == AzToolsFramework::Prefab::InvalidTemplateId)
{
// This has not been loaded yet, this is the case of being saved with a different name.
// Create it
m_rootInstance->m_containerEntity->AddComponent(aznew Prefab::EditorPrefabComponent());
HandleEntitiesAdded({m_rootInstance->m_containerEntity.get()});
AzToolsFramework::Prefab::PrefabDom dom;
bool success = AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(*m_rootInstance, dom);
if (!success)
{
AZ_Error("Prefab", false, "Failed to convert current root instance into a DOM when saving file '%.*s'", AZ_STRING_ARG(filename));
return false;
}
templateId = m_prefabSystemComponent->AddTemplate(relativePath, std::move(dom));
if (templateId == AzToolsFramework::Prefab::InvalidTemplateId)
{
AZ_Error("Prefab", false, "Couldn't add new template id '%i' when saving file '%.*s'", templateId, AZ_STRING_ARG(filename));
return false;
}
}
else
{
// The template is already loaded, this is the case of either saving as same name or different name(loaded from before).
// Update the template with the changes
AzToolsFramework::Prefab::PrefabDom dom;
bool success = AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(*m_rootInstance, dom);
if (!success)
{
AZ_Error("Prefab", false, "Failed to convert current root instance into a DOM when saving file '%.*s'", AZ_STRING_ARG(filename));
return false;
}
m_prefabSystemComponent->UpdatePrefabTemplate(templateId, dom);
}
Prefab::TemplateId prevTemplateId = m_rootInstance->GetTemplateId();
m_rootInstance->SetTemplateId(templateId);
if (prevTemplateId != Prefab::InvalidTemplateId && templateId != prevTemplateId)
{
// Make sure we only have one level template loaded at a time
m_prefabSystemComponent->RemoveTemplate(prevTemplateId);
}
AZStd::string out;
if (m_loaderInterface->SaveTemplateToString(m_rootInstance->GetTemplateId(), out))
{
const size_t bytesToWrite = out.size();
const size_t bytesWritten = stream.Write(bytesToWrite, out.data());
return bytesWritten == bytesToWrite;
}
return false;
}
Prefab::InstanceOptionalReference PrefabEditorEntityOwnershipService::CreatePrefab(
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Prefab::Instance>>&& nestedPrefabInstances,
const AZStd::string& filePath, Prefab::Instance& instanceToParentUnder)
AZ::IO::PathView filePath, Prefab::InstanceOptionalReference instanceToParentUnder)
{
AZStd::unique_ptr<Prefab::Instance> createdPrefabInstance =
m_prefabSystemComponent->CreatePrefab(entities, AZStd::move(nestedPrefabInstances), filePath);
if (!instanceToParentUnder)
{
instanceToParentUnder = *m_rootInstance;
}
if (createdPrefabInstance)
{
Prefab::Instance& addedInstance = instanceToParentUnder.AddInstance(AZStd::move(createdPrefabInstance));
Prefab::Instance& addedInstance = instanceToParentUnder->get().AddInstance(AZStd::move(createdPrefabInstance));
HandleEntitiesAdded({addedInstance.m_containerEntity.get()});
return addedInstance;
}
HandleEntitiesAdded(entities);
return AZStd::nullopt;
}
@@ -161,6 +272,12 @@ namespace AzToolsFramework
return *m_rootInstance;
}
void PrefabEditorEntityOwnershipService::OnEntityRemoved(AZ::EntityId entityId)
{
AzFramework::SliceEntityRequestBus::MultiHandler::BusDisconnect(entityId);
m_entitiesRemovedCallback({ entityId });
}
void PrefabEditorEntityOwnershipService::SetEntitiesAddedCallback(OnEntitiesAddedCallback onEntitiesAddedCallback)
{
m_entitiesAddedCallback = AZStd::move(onEntitiesAddedCallback);
@@ -176,6 +293,120 @@ namespace AzToolsFramework
m_validateEntitiesCallback = AZStd::move(validateEntitiesCallback);
}
void PrefabEditorEntityOwnershipService::StartPlayInEditor()
{
if (m_rootInstance && !m_playInEditorData.m_isEnabled)
{
// Construct the runtime entities and products
Prefab::TemplateReference templateReference = m_prefabSystemComponent->FindTemplate(m_rootInstance->GetTemplateId());
if (templateReference.has_value())
{
bool converterLoaded = m_playInEditorData.m_converter.IsLoaded();
if (!converterLoaded)
{
converterLoaded = m_playInEditorData.m_converter.LoadStackProfile("PlayInEditor");
}
if (converterLoaded)
{
// Use a random uuid as this is only a temporary source.
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext context(AZ::Uuid::CreateRandom());
Prefab::PrefabDom copy;
copy.CopyFrom(templateReference->get().GetPrefabDom(), copy.GetAllocator(), false);
context.AddPrefab(DefaultMainSpawnableName, AZStd::move(copy));
m_playInEditorData.m_converter.ProcessPrefab(context);
if (context.HasCompletedSuccessfully())
{
static constexpr size_t NoRootSpawnable = AZStd::numeric_limits<size_t>::max();
size_t rootSpawnableIndex = NoRootSpawnable;
// Create temporary assets from the processed data.
for (auto& product : context.GetProcessedObjects())
{
if (product.GetAssetType() == AZ::AzTypeInfo<AzFramework::Spawnable>::Uuid() &&
product.GetId() ==
AZStd::string::format("%s.%s", DefaultMainSpawnableName, AzFramework::Spawnable::FileExtension))
{
rootSpawnableIndex = m_playInEditorData.m_assets.size();
}
AZ::Data::AssetInfo info;
info.m_assetId = product.GetAsset().GetId();
info.m_assetType = product.GetAssetType();
info.m_relativePath = product.GetId();
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, product.GetAsset().GetId(), info);
m_playInEditorData.m_assets.emplace_back(product.ReleaseAsset().release(), AZ::Data::AssetLoadBehavior::Default);
}
if (rootSpawnableIndex != NoRootSpawnable)
{
m_playInEditorData.m_entities.Reset(m_playInEditorData.m_assets[rootSpawnableIndex]);
m_playInEditorData.m_entities.SpawnAllEntities();
}
}
else
{
AZ_Error("Prefab", false, "Failed to convert the prefab into assets.");
return;
}
}
else
{
AZ_Error("Prefab", false, "Failed to create a prefab processing stack from key 'PlayInEditor'.");
return;
}
m_rootInstance->GetNestedEntities([this](AZStd::unique_ptr<AZ::Entity>& entity)
{
AZ_Assert(entity, "Invalid entity found in root instance while starting play in editor.");
if (entity->GetState() == AZ::Entity::State::Active)
{
entity->Deactivate();
m_playInEditorData.m_deactivatedEntities.push_back(entity.get());
}
return true;
});
}
}
m_playInEditorData.m_isEnabled = true;
}
void PrefabEditorEntityOwnershipService::StopPlayInEditor()
{
if (m_rootInstance && m_playInEditorData.m_isEnabled)
{
auto end = m_playInEditorData.m_deactivatedEntities.rend();
for (auto it = m_playInEditorData.m_deactivatedEntities.rbegin(); it != end; ++it)
{
AZ_Assert(*it, "Invalid entity added to list for re-activation after play-in-editor stopped.");
(*it)->Activate();
}
m_playInEditorData.m_deactivatedEntities.clear();
m_playInEditorData.m_entities.DespawnAllEntities();
m_playInEditorData.m_entities.Alert(
[assets = AZStd::move(m_playInEditorData.m_assets)]([[maybe_unused]]uint32_t generation) mutable
{
for (auto& asset : assets)
{
if (asset)
{
// Explicitly release because this needs to happen before the asset is unregistered.
asset.Release();
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequestBus::Events::UnregisterAsset, asset.GetId());
}
}
AZ::ScriptSystemRequestBus::Broadcast(&AZ::ScriptSystemRequests::GarbageCollect);
});
m_playInEditorData.m_entities.Clear();
}
m_playInEditorData.m_isEnabled = false;
}
//////////////////////////////////////////////////////////////////////////
// Slice Buses implementation with Assert(false), this will exist only during Slice->Prefab
// development to pinpoint and replace specific calls to Slice system
@@ -15,8 +15,10 @@
#include <AzFramework/Entity/PrefabEntityOwnershipService.h>
#include <AzFramework/Entity/SliceEntityOwnershipServiceBus.h>
#include <AzFramework/Slice/SliceEntityBus.h>
#include <AzFramework/Spawnable/SpawnableEntitiesContainer.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
namespace AzToolsFramework
{
@@ -104,7 +106,9 @@ namespace AzToolsFramework
using OnEntitiesRemovedCallback = AzFramework::OnEntitiesRemovedCallback;
using ValidateEntitiesCallback = AzFramework::ValidateEntitiesCallback;
explicit PrefabEditorEntityOwnershipService(
static inline constexpr const char* DefaultMainSpawnableName = "Root";
PrefabEditorEntityOwnershipService(
const AzFramework::EntityContextId& entityContextId, AZ::SerializeContext* serializeContext);
~PrefabEditorEntityOwnershipService();
@@ -154,27 +158,44 @@ namespace AzToolsFramework
// To be removed in the future
bool LoadFromStream(AZ::IO::GenericStream& stream, bool remapIds,
EntityIdToEntityIdMap* idRemapTable = nullptr,
const AZ::ObjectStream::FilterDescriptor& filterDesc = AZ::ObjectStream::FilterDescriptor());
const AZ::ObjectStream::FilterDescriptor& filterDesc = AZ::ObjectStream::FilterDescriptor()) override;
void SetEntitiesAddedCallback(OnEntitiesAddedCallback onEntitiesAddedCallback) override;
void SetEntitiesRemovedCallback(OnEntitiesRemovedCallback onEntitiesRemovedCallback) override;
void SetValidateEntitiesCallback(ValidateEntitiesCallback validateEntitiesCallback) override;
bool LoadFromStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) override;
bool SaveToStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) override;
void StartPlayInEditor() override;
void StopPlayInEditor() override;
protected:
AZ::SliceComponent::SliceInstanceAddress GetOwningSlice() override;
private:
struct PlayInEditorData
{
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabConversionPipeline m_converter;
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> m_assets;
AZStd::vector<AZ::Entity*> m_deactivatedEntities;
AzFramework::SpawnableEntitiesContainer m_entities;
bool m_isEnabled{ false };
};
PlayInEditorData m_playInEditorData;
//////////////////////////////////////////////////////////////////////////
// PrefabSystemComponentInterface interface implementation
Prefab::InstanceOptionalReference CreatePrefab(
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Prefab::Instance>>&& nestedPrefabInstances,
const AZStd::string& filePath, Prefab::Instance& instanceToParentUnder) override;
AZ::IO::PathView filePath, Prefab::InstanceOptionalReference instanceToParentUnder) override;
Prefab::InstanceOptionalReference GetRootPrefabInstance() override;
//////////////////////////////////////////////////////////////////////////
void OnEntityRemoved(AZ::EntityId entityId);
OnEntitiesAddedCallback m_entitiesAddedCallback;
OnEntitiesRemovedCallback m_entitiesRemovedCallback;
ValidateEntitiesCallback m_validateEntitiesCallback;
@@ -185,7 +206,6 @@ namespace AzToolsFramework
AZStd::string m_rootPath;
AZStd::unique_ptr<Prefab::Instance> m_rootInstance;
Prefab::PrefabSystemComponentInterface* m_prefabSystemComponent;
Prefab::PrefabLoaderInterface* m_loaderInterface;
AzFramework::EntityContextId m_entityContextId;
AZ::SerializeContext m_serializeContext;
@@ -14,7 +14,7 @@
#if !defined(Q_MOC_RUN)
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/AssetBrowserProductThumbnail.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/ProductThumbnail.h>
#include <AzToolsFramework/MaterialBrowser/MaterialBrowserBus.h>
#endif
@@ -0,0 +1,80 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerWidgetInterface.h>
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationBus.h>
namespace AzToolsFramework
{
namespace Prefab
{
void EditorPrefabComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorPrefabComponent, EditorComponentBase>();
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<EditorPrefabComponent>("Prefab Component", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide)
->Attribute(AZ::Edit::Attributes::HideIcon, true)
->Attribute(
AZ::Edit::Attributes::SliceFlags,
AZ::Edit::SliceFlags::HideOnAdd | AZ::Edit::SliceFlags::PushWhenHidden |
AZ::Edit::SliceFlags::DontGatherReference);
}
}
}
void EditorPrefabComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("EditorPrefabInstanceContainerService"));
}
void EditorPrefabComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("EditorPrefabInstanceContainerService"));
}
void EditorPrefabComponent::Activate()
{
PrefabPublicInterface* prefabPublicInterface = AZ::Interface<PrefabPublicInterface>::Get();
if (prefabPublicInterface && prefabPublicInterface->IsLevelInstanceContainerEntity(GetEntityId()))
{
EntityOutlinerWidgetInterface* entityOutlinerWidgetInterface = AZ::Interface<EntityOutlinerWidgetInterface>::Get();
if (entityOutlinerWidgetInterface)
{
entityOutlinerWidgetInterface->SetRootEntity(GetEntityId());
}
}
PrefabInstanceContainerNotificationBus::Broadcast(
&PrefabInstanceContainerNotifications::OnPrefabComponentActivate, GetEntityId());
}
void EditorPrefabComponent::Deactivate()
{
PrefabInstanceContainerNotificationBus::Broadcast(
&PrefabInstanceContainerNotifications::OnPrefabComponentDeactivate, GetEntityId());
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -0,0 +1,35 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzToolsFramework
{
namespace Prefab
{
class EditorPrefabComponent : public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_COMPONENT(EditorPrefabComponent, "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}", EditorComponentBase);
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services);
private:
void Activate() override;
void Deactivate() override;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -26,6 +26,11 @@ namespace AzToolsFramework
namespace Prefab
{
Instance::Instance()
: Instance(AZStd::make_unique<AZ::Entity>())
{
}
Instance::Instance(AZStd::unique_ptr<AZ::Entity> containerEntity)
{
m_instanceEntityMapper = AZ::Interface<InstanceEntityMapperInterface>::Get();
@@ -41,27 +46,16 @@ namespace AzToolsFramework
"It is a requirement for the Prefab Instance class. "
"Check that it is being correctly initialized.");
m_containerEntity = AZStd::make_unique<AZ::Entity>();
m_alias = GenerateInstanceAlias();
m_containerEntity = containerEntity ? AZStd::move(containerEntity)
: AZStd::make_unique<AZ::Entity>();
EntityAlias containerEntityAlias = GenerateEntityAlias();
RegisterEntity(m_containerEntity->GetId(), containerEntityAlias);
}
Instance::~Instance()
{
// Clean up Instance associations.
if (m_templateId != InvalidTemplateId &&
!m_templateInstanceMapper->UnregisterInstance(*this))
{
AZ_Assert(false,
"Prefab - Attempted to unregister Instance from Template on file path '%s' with Id '%u'. "
"Instance may never have been registered or was unregistered early.",
m_templateSourcePath.c_str(),
m_templateId);
}
ClearEntities();
m_nestedInstances.clear();
Reset();
}
void Instance::Reflect(AZ::ReflectContext* context)
@@ -121,31 +115,31 @@ namespace AzToolsFramework
return m_linkId;
}
const AZStd::string& Instance::GetTemplateSourcePath() const
const AZ::IO::Path& Instance::GetTemplateSourcePath() const
{
return m_templateSourcePath;
}
void Instance::SetTemplateSourcePath(AZStd::string sourcePath)
void Instance::SetTemplateSourcePath(AZ::IO::PathView sourcePath)
{
m_templateSourcePath = AZStd::move(sourcePath);
AZStd::string filename;
if (AZ::StringFunc::Path::GetFileName(m_templateSourcePath.c_str(), filename))
{
m_containerEntity->SetName(filename);
}
m_templateSourcePath = sourcePath;
m_containerEntity->SetName(sourcePath.Filename().Native());
}
bool Instance::AddEntity(AZ::Entity& entity)
{
EntityAlias newEntityAlias = GenerateEntityAlias();
if (!RegisterEntity(entity.GetId(), newEntityAlias))
return AddEntity(entity, newEntityAlias);
}
bool Instance::AddEntity(AZ::Entity& entity, EntityAlias entityAlias)
{
if (!RegisterEntity(entity.GetId(), entityAlias))
{
return false;
}
if (!m_entities.emplace(AZStd::make_pair(newEntityAlias, &entity)).second)
if (!m_entities.emplace(AZStd::make_pair(entityAlias, &entity)).second)
{
return false;
}
@@ -167,6 +161,7 @@ namespace AzToolsFramework
"This happens when the entity is not correctly removed from all the prefab system entity maps.",
entityId.ToString().c_str(), m_templateSourcePath.c_str());
}
return DetachEntity(entityAliasToRemove);
}
@@ -217,6 +212,31 @@ namespace AzToolsFramework
}
}
void Instance::Reset()
{
// Clean up Instance associations.
if (m_templateId != InvalidTemplateId && !m_templateInstanceMapper->UnregisterInstance(*this))
{
AZ_Assert(
false,
"Prefab - Attempted to unregister Instance from Template on file path '%s' with Id '%u'. "
"Instance may never have been registered or was unregistered early.",
m_templateSourcePath.c_str(), m_templateId);
}
ClearEntities();
m_nestedInstances.clear();
if (m_containerEntity)
{
m_instanceEntityMapper->UnregisterEntity(m_containerEntity->GetId());
m_containerEntity.reset(aznew AZ::Entity());
RegisterEntity(m_containerEntity->GetId(), GenerateEntityAlias());
}
}
void Instance::RemoveEntities(
const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter)
{
@@ -400,7 +420,9 @@ namespace AzToolsFramework
}
}
entities.reserve(entities.size() + currentInstance->m_entities.size());
// Size increases by 1 for each instance because we have to count the container entity also.
entities.reserve(entities.size() + currentInstance->m_entities.size() + 1);
entities.push_back(m_containerEntity.get());
for (const auto& entityByAlias : currentInstance->m_entities)
{
entities.push_back(entityByAlias.second.get());
@@ -441,55 +463,53 @@ namespace AzToolsFramework
return nestedInstanceAliases;
}
AliasPath Instance::GetAbsoluteInstanceAliasPath() const
{
// Reset the path using our preferred separator
AliasPath aliasPathResult = AliasPath(s_aliasPathSeparator);
const Instance* currentInstance = this;
// If no parent instance we are a root instance and our absolute path is empty
AZStd::vector<const Instance*> pathOfInstances;
while (currentInstance->m_parent)
{
pathOfInstances.emplace_back(currentInstance);
currentInstance = currentInstance->m_parent;
}
pathOfInstances.emplace_back(currentInstance);
for (auto instanceIter = pathOfInstances.rbegin(); instanceIter != pathOfInstances.rend(); ++instanceIter)
{
aliasPathResult.Append((*instanceIter)->m_alias);
}
return aliasPathResult;
}
EntityAlias Instance::GenerateEntityAlias()
{
return "Entity_" + AZ::Entity::MakeId().ToString();
return AZStd::string::format("Entity_%s", AZ::Entity::MakeId().ToString().c_str());
}
InstanceAlias Instance::GenerateInstanceAlias()
{
return "Instance_" + AZ::Entity::MakeId().ToString();
return AZStd::string::format("Instance_%s", AZ::Entity::MakeId().ToString().c_str());
}
void Instance::InitializeNestedEntities()
void Instance::ActivateContainerEntity()
{
InitializeEntities();
AZ_Assert(m_containerEntity, "Container entity of instance is null.");
for (const auto&[instanceAlias, instance] : m_nestedInstances)
if (AZ::Entity::State::Constructed == m_containerEntity->GetState())
{
instance->InitializeNestedEntities();
m_containerEntity->Init();
}
}
void Instance::InitializeEntities()
{
for (const auto&[entityAlias, entity] : m_entities)
if (AZ::Entity::State::Init == m_containerEntity->GetState())
{
if (AZ::Entity::State::Constructed == entity->GetState())
{
entity->Init();
}
}
}
void Instance::ActivateNestedEntities()
{
ActivateEntities();
for (const auto&[instanceAlias, instance] : m_nestedInstances)
{
instance->ActivateNestedEntities();
}
}
void Instance::ActivateEntities()
{
for (const auto&[entityAlias, entity] : m_entities)
{
if(AZ::Entity::State::Init == entity->GetState())
{
entity->Activate();
}
m_containerEntity->Activate();
}
}
@@ -545,5 +565,38 @@ namespace AzToolsFramework
{
return m_containerEntity->GetId();
}
bool Instance::HasContainerEntity() const
{
return m_containerEntity.get() != nullptr;
}
EntityOptionalReference Instance::GetContainerEntity()
{
if (m_containerEntity)
{
return *m_containerEntity;
}
return AZStd::nullopt;
}
EntityOptionalConstReference Instance::GetContainerEntity() const
{
if (m_containerEntity)
{
return *m_containerEntity;
}
return AZStd::nullopt;
}
void Instance::SetContainerEntity(AZ::Entity& entity)
{
m_containerEntity.reset(&entity);
}
AZStd::unique_ptr<AZ::Entity> Instance::DetachContainerEntity()
{
return AZStd::move(m_containerEntity);
}
}
}
@@ -13,6 +13,7 @@
#pragma once
#include <AzCore/Component/EntityId.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/RTTI.h>
@@ -38,8 +39,10 @@ namespace AzToolsFramework
class InstanceEntityMapperInterface;
class TemplateInstanceMapperInterface;
using InstanceAlias = AZStd::string;
using AliasPath = AZ::IO::Path;
using AliasPathView = AZ::IO::PathView;
using EntityAlias = AZStd::string;
using InstanceAlias = AZStd::string;
class Instance;
using EntityAliasOptionalReference = AZStd::optional<AZStd::reference_wrapper<EntityAlias>>;
@@ -47,6 +50,8 @@ namespace AzToolsFramework
using InstanceOptionalConstReference = AZStd::optional<AZStd::reference_wrapper<const Instance>>;
using InstanceSet = AZStd::unordered_set<Instance*>;
using InstanceSetConstReference = AZStd::optional<AZStd::reference_wrapper<const InstanceSet>>;
using EntityOptionalReference = AZStd::optional<AZStd::reference_wrapper<AZ::Entity>>;
using EntityOptionalConstReference = AZStd::optional<AZStd::reference_wrapper<const AZ::Entity>>;
// A prefab instance is the container for when a Prefab Template is Instantiated.
class Instance
@@ -62,6 +67,7 @@ namespace AzToolsFramework
using EntityList = AZStd::vector<AZ::Entity*>;
Instance();
explicit Instance(AZStd::unique_ptr<AZ::Entity> containerEntity);
virtual ~Instance();
Instance(const Instance& rhs) = delete;
@@ -72,14 +78,17 @@ namespace AzToolsFramework
const TemplateId& GetTemplateId() const;
void SetTemplateId(const TemplateId& templateId);
const AZStd::string& GetTemplateSourcePath() const;
void SetTemplateSourcePath(AZStd::string sourcePath);
const AZ::IO::Path& GetTemplateSourcePath() const;
void SetTemplateSourcePath(AZ::IO::PathView sourcePath);
bool AddEntity(AZ::Entity& entity);
bool AddEntity(AZ::Entity& entity, EntityAlias entityAlias);
AZStd::unique_ptr<AZ::Entity> DetachEntity(const AZ::EntityId& entityId);
void DetachNestedEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
void RemoveNestedEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter);
void Reset();
Instance& AddInstance(AZStd::unique_ptr<Instance> instance);
AZStd::unique_ptr<Instance> DetachNestedInstance(const InstanceAlias& instanceAlias);
@@ -128,17 +137,7 @@ namespace AzToolsFramework
*/
AZStd::vector<InstanceAlias> GetNestedInstanceAliases(TemplateId templateId) const;
/**
* Initializes all entities, including those in nested entities
*/
void InitializeNestedEntities();
void InitializeEntities();
/**
* Activates all entities, including those in nested entities
*/
void ActivateNestedEntities();
void ActivateEntities();
void ActivateContainerEntity();
InstanceOptionalReference FindNestedInstance(const InstanceAlias& nestedInstanceAlias);
@@ -158,6 +157,18 @@ namespace AzToolsFramework
AZ::EntityId GetContainerEntityId() const;
bool HasContainerEntity() const;
EntityOptionalReference GetContainerEntity();
EntityOptionalConstReference GetContainerEntity() const;
void SetContainerEntity(AZ::Entity& entity);
AZStd::unique_ptr<AZ::Entity> DetachContainerEntity();
static EntityAlias GenerateEntityAlias();
AliasPath GetAbsoluteInstanceAliasPath() const;
protected:
/**
* Gets the entities owned by this instance
@@ -165,6 +176,7 @@ namespace AzToolsFramework
void GetEntities(EntityList& entities, bool includeNestedEntities = false);
private:
static constexpr const char s_aliasPathSeparator = '/';
void ClearEntities();
@@ -174,7 +186,6 @@ namespace AzToolsFramework
bool RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias);
AZStd::unique_ptr<AZ::Entity> DetachEntity(const EntityAlias& entityAlias);
static EntityAlias GenerateEntityAlias();
static InstanceAlias GenerateInstanceAlias();
// Provide access to private data members in the serializer
@@ -202,7 +213,7 @@ namespace AzToolsFramework
InstanceAlias m_alias;
// The source path of the template this instance represents
AZStd::string m_templateSourcePath;
AZ::IO::Path m_templateSourcePath;
// The unique ID of the template this Instance belongs to.
TemplateId m_templateId = InvalidTemplateId;
@@ -11,7 +11,10 @@
*/
#include <AzCore/Component/Entity.h>
#include <AzCore/Math/Sfmt.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Utils/TypeHash.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapper.h>
@@ -44,19 +47,15 @@ namespace AzToolsFramework
if (!inputAlias.empty())
{
if (m_isEntityReference)
{
m_unresolvedEntityAliases[m_loadingInstance].emplace_back(inputAlias, &outputValue);
}
else
{
mappedValue = AZ::Entity::MakeId();
AliasPath absoluteEntityPath = m_loadingInstance->GetAbsoluteInstanceAliasPath();
absoluteEntityPath.Append(inputAlias);
absoluteEntityPath = absoluteEntityPath.LexicallyNormal();
if (m_loadingInstance->RegisterEntity(mappedValue, inputAlias))
{
m_resolvedEntityAliases[m_loadingInstance].emplace_back(inputAlias, mappedValue);
}
else
mappedValue = GenerateEntityIdForAliasPath(absoluteEntityPath, m_randomSeed);
if (!m_isEntityReference)
{
if (!m_loadingInstance->RegisterEntity(mappedValue, inputAlias))
{
mappedValue = AZ::EntityId(AZ::EntityId::InvalidEntityId);
context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
@@ -67,7 +66,7 @@ namespace AzToolsFramework
outputValue = mappedValue;
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Success, "Succesfully mapped Entity Id For Prefab Instance load");
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Success, "Successfully mapped Entity Id For Prefab Instance load");
}
AZ::JsonSerializationResult::Result InstanceEntityIdMapper::MapIdToJson(rapidjson::Value& outputValue,
@@ -77,9 +76,6 @@ namespace AzToolsFramework
if (!m_storingInstance)
{
AZ_Assert(false,
"Attempted to map an EntityId in Prefab Instance without setting the Storing Prefab Instance");
return context.Report(JSR::Tasks::WriteValue, JSR::Outcomes::DefaultsUsed,
"Attempted to map an EntityId in Prefab Instance without setting the Storing Prefab Instance");
}
@@ -104,8 +100,6 @@ namespace AzToolsFramework
AZStd::string defaultErrorMessage =
"Entity with Id " + inputValue.ToString() +
" could not be found within its owning instance. Defaulting to invalid Id for Store.";
AZ_Assert(false, defaultErrorMessage.c_str());
context.Report(JSR::Tasks::WriteValue, JSR::Outcomes::DefaultsUsed, defaultErrorMessage);
}
}
@@ -113,13 +107,40 @@ namespace AzToolsFramework
outputValue.SetString(rapidjson::StringRef(mappedValue.c_str(), mappedValue.length()), context.GetJsonAllocator());
return context.Report(JSR::Tasks::WriteValue, JSR::Outcomes::Success, "Succesfully mapped Entity Id For Prefab Instance store");
return context.Report(JSR::Tasks::WriteValue, JSR::Outcomes::Success, "Successfully mapped Entity Id For Prefab Instance store");
}
void InstanceEntityIdMapper::SetEntityIdGenerationApproach(EntityIdGenerationApproach approach)
{
m_entityIdGenerationApproach = approach;
if (m_entityIdGenerationApproach == EntityIdGenerationApproach::Hashed)
{
m_randomSeed = SeedKey;
}
else if (m_entityIdGenerationApproach == EntityIdGenerationApproach::Random)
{
m_randomSeed = AZ::Sfmt::GetInstance().Rand64();
// Sanity check to avoid collision with hashed mode
if (m_randomSeed == SeedKey)
{
m_randomSeed++;
}
}
else
{
AZ_Assert(false, "Unsupported option in EntityIdGenerationApproach encountered."
" Defaulting to Hashed");
m_randomSeed = SeedKey;
}
}
void InstanceEntityIdMapper::SetStoringInstance(const Instance& storingInstance)
{
m_storingInstance = &storingInstance;
GetAbsoluteInstanceAliasPath(m_storingInstance, m_instanceAbsolutePath);
m_instanceAbsolutePath = m_storingInstance->GetAbsoluteInstanceAliasPath();
}
void InstanceEntityIdMapper::SetLoadingInstance(Instance& loadingInstance)
@@ -127,78 +148,6 @@ namespace AzToolsFramework
m_loadingInstance = &loadingInstance;
}
void InstanceEntityIdMapper::FixUpUnresolvedEntityReferences()
{
// Nothing to resolve
if (m_unresolvedEntityAliases.empty())
{
return;
}
// Calculate the absolute path to each instance based on its position in the hierarchy
// We'll use this to generate the absolute paths of resolved and unresolved aliases
AZStd::unordered_map<Instance*, AliasPath> absoluteInstanceAliasPaths;
absoluteInstanceAliasPaths.reserve(m_resolvedEntityAliases.bucket_count());
// Also calculate the absolute path to each resolved entity based on its position in the hierarchy
AZStd::unordered_map<AliasPath, AZ::EntityId> resolvedAbsoluteEntityAliasPaths;
for (auto& [instance, resolvedAliasIdList] : m_resolvedEntityAliases)
{
AliasPath absoluteInstancePath;
GetAbsoluteInstanceAliasPath(instance, absoluteInstancePath);
absoluteInstanceAliasPaths.emplace(instance, absoluteInstancePath);
for (auto& [entityAlias, entityId] : resolvedAliasIdList)
{
resolvedAbsoluteEntityAliasPaths.emplace(absoluteInstancePath / entityAlias, entityId);
}
}
// Using the absolute paths of the instances containing the unresolved aliases
// Attempt to find a match within the resolved paths
// A match will allow us to update the unresolved reference id to match the id it's referencing
for (auto& [instance, unresolvedAliasIdList] : m_unresolvedEntityAliases)
{
auto findResolvedInstance = absoluteInstanceAliasPaths.find(instance);
if (findResolvedInstance != absoluteInstanceAliasPaths.end())
{
AliasPath& absoluteInstancePath = findResolvedInstance->second;
for (auto& [entityAlias, entityPointer] : unresolvedAliasIdList)
{
AliasPath absoluteEntityReferencePath = (absoluteInstancePath / entityAlias).LexicallyNormal();
auto foundResolvedPath =
resolvedAbsoluteEntityAliasPaths.find(absoluteEntityReferencePath);
if (foundResolvedPath != resolvedAbsoluteEntityAliasPaths.end())
{
*entityPointer = foundResolvedPath->second;
}
else
{
AZ_Warning("Prefabs", false,
"Unable to resolve entity reference alias path [%s] while loading Prefab instance. "
"The reference was likely made on a parent or sibling prefab without the use of an override. "
"Defaulting the reference to an invalid EntityId.",
absoluteEntityReferencePath.String().c_str());
}
}
}
else
{
AZ_Assert(false, "Prefabs - "
"Attempted to resolve entity alias path(s) but the instance owning the unresolved reference has no entities. "
"An Entity Reference can only come from an entity/component property.");
}
}
return;
}
EntityAlias InstanceEntityIdMapper::ResolveReferenceId(const AZ::EntityId& entityId)
{
// Acquire the owning instance of our entity
@@ -209,7 +158,7 @@ namespace AzToolsFramework
AliasPath relativeEntityAliasPath;
if (!owningInstanceReference)
{
AZ_Assert(false,
AZ_Warning("Prefabs", false,
"Prefab - EntityIdMapper: Entity with Id %s has no registered owning instance",
entityId.ToString().c_str());
@@ -220,24 +169,16 @@ namespace AzToolsFramework
// Build out the absolute path of this alias
// so we can compare it to the absolute path of our currently scoped instance
GetAbsoluteInstanceAliasPath(owningInstance, relativeEntityAliasPath);
relativeEntityAliasPath = owningInstance->GetAbsoluteInstanceAliasPath();
relativeEntityAliasPath.Append(owningInstance->GetEntityAlias(entityId)->get());
return relativeEntityAliasPath.LexicallyRelative(m_instanceAbsolutePath).String();
}
void InstanceEntityIdMapper::GetAbsoluteInstanceAliasPath(const Instance* instance, AliasPath& aliasPathResult)
AZ::EntityId InstanceEntityIdMapper::GenerateEntityIdForAliasPath(const AliasPathView& aliasPath, uint64_t seedKey)
{
// Reset the path using our preferred seperator
aliasPathResult = AliasPath(m_aliasPathSeperator);
const Instance* currentInstance = instance;
// If no parent instance we are a root instance and our absolute path is empty
while (currentInstance->m_parent)
{
aliasPathResult.Append(currentInstance->m_alias);
currentInstance = currentInstance->m_parent;
}
const AZ::HashValue64 seed = aznumeric_cast<AZ::HashValue64>(seedKey);
return AZ::EntityId(aznumeric_cast<AZ::u64>((AZ::TypeHash64(aliasPath.Native().data(), seed))));
}
}
}
@@ -25,41 +25,37 @@ namespace AzToolsFramework
: public AZ::JsonEntityIdSerializer::JsonEntityIdMapper
{
public:
enum class EntityIdGenerationApproach
{
Hashed, //! Creates an entity id that's stable between runs. (Default)
Random //! Entities get a new randomly generate id.
};
AZ_RTTI(InstanceEntityIdMapper, "{EA76C3A7-1210-4DFB-A1C0-2F8E8B0B888E}", AZ::JsonEntityIdSerializer::JsonEntityIdMapper);
static inline constexpr uint64_t SeedKey = 5915587277; // Random prime number
AZ::JsonSerializationResult::Result MapJsonToId(AZ::EntityId& outputValue, const rapidjson::Value& inputValue, AZ::JsonDeserializerContext& context) override;
AZ::JsonSerializationResult::Result MapIdToJson(rapidjson::Value& outputValue, const AZ::EntityId& inputValue, AZ::JsonSerializerContext& context) override;
void SetEntityIdGenerationApproach(EntityIdGenerationApproach approach);
void SetStoringInstance(const Instance& storingInstance);
void SetLoadingInstance(Instance& loadingInstance);
/**
* Fixes up any unresolved entity references by assigning them to the id value of the entity it references.
* This is done by computing the absolute alias paths of all EntityIds and EntityId references
* then matching the references to the id values they reference.
* During a load instance and alias information is stored to build out these paths,
* but will be incomplete until the whole Load call is finished.
* Calling this after Load and the mapper have finished will give complete information
* on all entities and references discovered in the load
*/
void FixUpUnresolvedEntityReferences();
static AZ::EntityId GenerateEntityIdForAliasPath(const AliasPathView& aliasPath, uint64_t seedKey = SeedKey);
private:
using AliasPath = AZ::IO::Path;
EntityAlias ResolveReferenceId(const AZ::EntityId& entityId);
void GetAbsoluteInstanceAliasPath(const Instance* instance, AliasPath& aliasPathResult);
AliasPath m_instanceAbsolutePath;
const Instance* m_storingInstance = nullptr;
Instance* m_loadingInstance = nullptr;
static constexpr const char m_aliasPathSeperator = '/';
uint64_t m_randomSeed = SeedKey;
AZStd::unordered_map<Instance*, AZStd::vector<AZStd::pair<EntityAlias, AZ::EntityId>>> m_resolvedEntityAliases;
AZStd::unordered_map<Instance*, AZStd::vector<AZStd::pair<EntityAlias, AZ::EntityId*>>> m_unresolvedEntityAliases;
EntityIdGenerationApproach m_entityIdGenerationApproach { EntityIdGenerationApproach::Hashed };
};
}
}
@@ -13,6 +13,7 @@
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
namespace AZ
{
@@ -0,0 +1,28 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Prefab/Instance/InstanceEntityScrubber.h>
namespace AzToolsFramework
{
namespace Prefab
{
InstanceEntityScrubber::InstanceEntityScrubber(Instance::EntityList& entities)
: m_entities(entities)
{}
void InstanceEntityScrubber::AddEntitiesToScrub(const EntityList& entities)
{
m_entities.insert(m_entities.end(), entities.begin(), entities.end());
}
}
}
@@ -0,0 +1,44 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/vector.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
namespace AZ
{
class Entity;
}
namespace AzToolsFramework
{
using EntityList = AZStd::vector<AZ::Entity*>;
namespace Prefab
{
//! Collects the entities added during deserialization
class InstanceEntityScrubber
{
public:
AZ_RTTI(InstanceEntityScrubber, "{0BC12562-C240-48AD-89C6-EDF572C9B485}");
explicit InstanceEntityScrubber(Instance::EntityList& entities);
void AddEntitiesToScrub(const EntityList& entities);
private:
Instance::EntityList& m_entities;
};
}
}
@@ -13,9 +13,11 @@
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityScrubber.h>
#include <AzToolsFramework/Prefab/Instance/InstanceSerializer.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
namespace AzToolsFramework
@@ -46,8 +48,8 @@ namespace AzToolsFramework
JSR::ResultCode result(JSR::Tasks::WriteValue);
{
AZ::ScopedContextPath subPathSource(context, "m_templateSourcePath");
const AZStd::string* sourcePath = &instance->GetTemplateSourcePath();
const AZStd::string* defaultSourcePath = defaultInstance ? &defaultInstance->GetTemplateSourcePath() : nullptr;
const AZStd::string* sourcePath = &(instance->GetTemplateSourcePath().Native());
const AZStd::string* defaultSourcePath = defaultInstance ? &defaultInstance->GetTemplateSourcePath().Native() : nullptr;
result = ContinueStoringToJsonObjectField(outputValue, "Source", sourcePath, defaultSourcePath, azrtti_typeid<AZStd::string>(), context);
}
@@ -64,7 +66,6 @@ namespace AzToolsFramework
AZ::ScopedContextPath subPathEntities(context, "m_entities");
const Instance::AliasToEntityMap* entities = &instance->m_entities;
const Instance::AliasToEntityMap* defaultEntities = defaultInstance ? &defaultInstance->m_entities : nullptr;
JSR::ResultCode resultEntities =
ContinueStoringToJsonObjectField(outputValue, "Entities",
entities, defaultEntities, azrtti_typeid<Instance::AliasToEntityMap>(), context);
@@ -116,6 +117,15 @@ namespace AzToolsFramework
AZ_Assert(prefabSystemComponentInterface,
"PrefabSystemComponentInterface could not be found. It is required to load Prefab Instances");
PrefabLoaderInterface* loaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
AZ_Assert(
loaderInterface,
"PrefabLoaderInterface could not be found. It is required to load Prefab Instances");
// Make sure we have a relative path
instance->m_templateSourcePath = loaderInterface->GetRelativePathToProject(instance->m_templateSourcePath);
TemplateId templateId = prefabSystemComponentInterface->GetTemplateIdFromFilePath(instance->GetTemplateSourcePath());
instance->SetTemplateId(templateId);
@@ -125,25 +135,35 @@ namespace AzToolsFramework
}
{
// An already filled instance should be cleared if inputValue's Instances member is empty
// The Json serializer will not do this by default as it will not attempt to load a missing member
if (!instance->m_nestedInstances.empty() && !inputValue.HasMember("Instances"))
{
instance->m_nestedInstances.clear();
}
instance->m_nestedInstances.clear();
JSR::ResultCode instanceResult =
ContinueLoadingFromJsonObjectField(&instance->m_nestedInstances, azrtti_typeid<Instance::AliasToInstanceMap>(), inputValue, "Instances", context);
if (instanceResult.GetProcessing() != JSR::Processing::Halted)
// Load nested instances iteratively
// We want to first create the nested instance object and assign its alias and parent pointer
// These values are needed for the idmapper to properly resolve alias paths
auto instancesMemberIter = inputValue.FindMember("Instances");
if (instancesMemberIter != inputValue.MemberEnd() && instancesMemberIter->value.IsObject())
{
for (auto& nestedInstance : instance->m_nestedInstances)
for (auto& instanceIter : instancesMemberIter->value.GetObject())
{
nestedInstance.second->m_parent = instance;
nestedInstance.second->m_alias = nestedInstance.first;
const rapidjson::Value& instanceAliasValue = instanceIter.name;
InstanceAlias instanceAlias(instanceAliasValue.GetString(),
instanceAliasValue.GetStringLength());
AZStd::unique_ptr<Instance> nestedInstance = AZStd::make_unique<Instance>();
nestedInstance->m_alias = instanceAlias;
nestedInstance->m_parent = instance;
result.Combine(
ContinueLoading(&nestedInstance, azrtti_typeid<decltype(nestedInstance)>(),
instanceIter.value, context));
instance->m_nestedInstances.emplace(
instanceAlias,
AZStd::move(nestedInstance));
}
}
result.Combine(instanceResult);
}
// An already filled instance should be cleared if inputValue's Entities member is empty
@@ -153,6 +173,7 @@ namespace AzToolsFramework
if (instance->m_containerEntity)
{
instance->m_instanceEntityMapper->UnregisterEntity(instance->m_containerEntity->GetId());
instance->m_containerEntity.reset();
}
if (idMapper && *idMapper)
@@ -168,7 +189,10 @@ namespace AzToolsFramework
}
{
result.Combine(ContinueLoadingFromJsonObjectField(&instance->m_entities, azrtti_typeid<Instance::AliasToEntityMap>(), inputValue, "Entities", context));
JSR::ResultCode entitiesResult = ContinueLoadingFromJsonObjectField(
&instance->m_entities, azrtti_typeid<Instance::AliasToEntityMap>(), inputValue, "Entities", context);
AddEntitiesToScrub(instance, context);
result.Combine(entitiesResult);
}
{
@@ -179,5 +203,29 @@ namespace AzToolsFramework
result.GetProcessing() == JSR::Processing::Completed ? "Succesfully loaded instance information for prefab." :
"Failed to load instance information for prefab");
}
}
void JsonInstanceSerializer::AddEntitiesToScrub(const Instance* instance, AZ::JsonDeserializerContext& jsonDeserializerContext)
{
EntityList entitiesInInstance;
entitiesInInstance.reserve(instance->m_entities.size() + 1);
if (instance->m_containerEntity->GetId().IsValid())
{
entitiesInInstance.emplace_back(instance->m_containerEntity.get());
}
for (const auto& [entityAlias, entity] : instance->m_entities)
{
entitiesInInstance.emplace_back(entity.get());
}
InstanceEntityScrubber** instanceEntityScrubber = jsonDeserializerContext.GetMetadata().Find<InstanceEntityScrubber*>();
if (instanceEntityScrubber && (*instanceEntityScrubber))
{
(*instanceEntityScrubber)->AddEntitiesToScrub(entitiesInInstance);
}
}
} // namespace Prefab
}
@@ -34,6 +34,11 @@ namespace AzToolsFramework
AZ::JsonSerializationResult::Result Load(void* outputValue, const AZ::Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
AZ::JsonDeserializerContext& context) override;
private:
//! Adds the entities of an instance to a InstanceEntityScrubber object in the metadata of JsonDeserializerContext
//! so that they can be scrubbed later.
void AddEntitiesToScrub(const Instance* instance, AZ::JsonDeserializerContext& jsonDeserializercontext);
};
}
}
@@ -14,7 +14,10 @@
#include <AzCore/Component/EntityId.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Link/Link.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
namespace AzToolsFramework
{
@@ -40,12 +43,14 @@ namespace AzToolsFramework
const PrefabDom& modifiedState, const LinkId linkId) = 0;
//! Updates the affected template for a given entityId using the providedPatch
virtual void PatchEntityInTemplate(PrefabDomValue& providedPatch, const AZ::EntityId& entityId) = 0;
virtual bool PatchEntityInTemplate(PrefabDomValue& providedPatch, const AZ::EntityId& entityId) = 0;
virtual void PatchEntityInTemplate(PrefabDomValue& providedPatch, const EntityAlias& entityAlias, const TemplateId& templateId) = 0;
virtual bool PatchEntityInTemplate(PrefabDomValue& providedPatch, const EntityAlias& entityAlias, const TemplateId& templateId) = 0;
virtual void AppendEntityAliasToPatchPaths(PrefabDom& providedPatch, const AZ::EntityId& entityId) = 0;
//! Updates the template links (updating instances) for the given templateId using the providedPatch
virtual void PatchTemplate(PrefabDomValue& providedPatch, const AzToolsFramework::Prefab::TemplateId& templateId) = 0;
virtual void PatchTemplate(PrefabDomValue& providedPatch, const TemplateId& templateId) = 0;
virtual void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) = 0;
@@ -81,7 +81,7 @@ namespace AzToolsFramework
bool InstanceToTemplatePropagator::GeneratePatch(PrefabDom& generatedPatch, const PrefabDom& initialState,
const PrefabDom& modifiedState)
{
//generate patch using jsonserialization CreatePatch
//generate patch using json serialization CreatePatch
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::CreatePatch(generatedPatch,
generatedPatch.GetAllocator(), initialState, modifiedState, AZ::JsonMergeApproach::JsonPatch);
@@ -107,11 +107,17 @@ namespace AzToolsFramework
return result.GetProcessing() != AZ::JsonSerializationResult::Processing::Halted;
}
void InstanceToTemplatePropagator::PatchEntityInTemplate(PrefabDomValue& providedPatch, const AZ::EntityId& entityId)
bool InstanceToTemplatePropagator::PatchEntityInTemplate(PrefabDomValue& providedPatch, const AZ::EntityId& entityId)
{
InstanceOptionalReference instanceOptionalReference = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
AZ_Error("Prefab", instanceOptionalReference,
"Failed to find an owning instance for the entity with id %llu.", static_cast<AZ::u64>(entityId));
if (!instanceOptionalReference)
{
AZ_Error(
"Prefab", false, "Failed to find an owning instance for the entity with id %llu.",
static_cast<AZ::u64>(entityId));
return false;
}
//get template space associated with instance
Instance& instance = instanceOptionalReference->get();
@@ -119,33 +125,95 @@ namespace AzToolsFramework
//alias entity goes by in template -> get via owning instance map
AZStd::optional<EntityAlias> entityAlias = instance.GetEntityAlias(entityId);
AZ_Error("Prefab", entityAlias != AZStd::nullopt,
"Failed to find an entity alias for the provided entity");
if (!entityAlias)
{
AZ_Error("Prefab", false, "Failed to find an entity alias for the provided entity");
return false;
}
PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
PatchEntityInTemplate(providedPatch, entityAlias.value(), templateId);
return PatchEntityInTemplate(providedPatch, entityAlias.value(), templateId);
}
void InstanceToTemplatePropagator::PatchEntityInTemplate(PrefabDomValue& providedPatch, const EntityAlias& entityAlias, const TemplateId& templateId)
bool InstanceToTemplatePropagator::PatchEntityInTemplate(PrefabDomValue& providedPatch, const EntityAlias& entityAlias, const TemplateId& templateId)
{
PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
//query into the template dom for the alias
PrefabDomValueReference entityList = PrefabDomUtils::FindPrefabDomValue(templateDomReference, PrefabDomUtils::EntitiesName);
if (!entityList)
{
AZ_Error("Prefab", false, "Cannot patch entity in Template with id [%llu] because entity couldn't be found in the template", templateId);
return false;
}
PrefabDomValueReference entity = PrefabDomUtils::FindPrefabDomValue(entityList->get(), entityAlias.c_str());
AZ_Error("Prefab", entity != AZStd::nullopt, "Failed to aquire entity value reference")
if (!entity)
{
AZ_Error("Prefab", false, "Failed to aquire entity value reference");
return false;
}
//apply patch to section
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::ApplyPatch(entity->get(),
templateDomReference.GetAllocator(), providedPatch, AZ::JsonMergeApproach::JsonPatch);
AZ_Error("Prefab", result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success,
"Patch was not successfully applied")
AZ_Error("Prefab", result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success, "Patch was not successfully applied")
//update the Dom and trigger propogation
//trigger propagation
m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId);
return true;
}
void InstanceToTemplatePropagator::AppendEntityAliasToPatchPaths(PrefabDom& providedPatch, const AZ::EntityId& entityId)
{
if (!providedPatch.IsArray())
{
AZ_Error("Prefab", false, "Patch is not an array of updates. Update failed.");
return;
}
//create the prefix for the update - choosing between container and regular entities
AZStd::string prefix = "/";
//grab the owning instance so we can use the entityIdMapper in settings
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
AZ_Assert(owningInstance != AZStd::nullopt, "Owning Instance is null");
bool isContainerEntity = entityId == owningInstance->get().GetContainerEntityId();
if (isContainerEntity)
{
prefix += PrefabDomUtils::ContainerEntityName;
}
else
{
EntityAliasOptionalReference entityAliasRef = owningInstance->get().GetEntityAlias(entityId);
prefix += PrefabDomUtils::EntitiesName;
prefix += "/";
prefix += entityAliasRef->get();
}
//update all entities or just the single container
for (auto& patchValue : providedPatch.GetArray())
{
auto pathIter = patchValue.FindMember("path");
if (pathIter == patchValue.MemberEnd() || !(pathIter->value.IsString()))
{
AZ_Error("Prefab", false, "Was not able to find path member within patch dom. "
"A non prefab dom patch may have been passed in.");
continue;
}
AZStd::string path = prefix + pathIter->value.GetString();
pathIter->value.SetString(path.c_str(), path.length(), providedPatch.GetAllocator());
}
}
void InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, const TemplateId& templateId)
@@ -159,9 +227,10 @@ namespace AzToolsFramework
AZ_Error("Prefab", result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success,
"Patch was not successfully applied");
//update the Dom and trigger propogation
//trigger propagation
if (result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success)
{
m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, true);
m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId);
}
}
@@ -31,8 +31,10 @@ namespace AzToolsFramework
bool GeneratePatch(PrefabDom& generatedPatch, const PrefabDom& initialState, const PrefabDom& modifiedState) override;
bool GeneratePatchForLink(PrefabDom& generatedPatch, const PrefabDom& initialState,
const PrefabDom& modifiedState, LinkId linkId) override;
void PatchEntityInTemplate(PrefabDomValue& providedPatch, const AZ::EntityId& entityId) override;
void PatchEntityInTemplate(PrefabDomValue& providedPatch, const EntityAlias& entityAlias, const TemplateId& templateId) override;
bool PatchEntityInTemplate(PrefabDomValue& providedPatch, const AZ::EntityId& entityId) override;
bool PatchEntityInTemplate(PrefabDomValue& providedPatch, const EntityAlias& entityAlias, const TemplateId& templateId) override;
void AppendEntityAliasToPatchPaths(PrefabDom& providedPatch, const AZ::EntityId& entityId) override;
InstanceOptionalReference GetTopMostInstanceInHierarchy(AZ::EntityId entityId);
@@ -40,8 +42,11 @@ namespace AzToolsFramework
void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) override;
private:
void AddPatchesToLink(PrefabDom& patches, Link& link);
private:
InstanceEntityMapperInterface* m_instanceEntityMapperInterface;
PrefabSystemComponentInterface* m_prefabSystemComponentInterface;
};
@@ -12,12 +12,16 @@
#include <AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Interface/Interface.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/TemplateInstanceMapperInterface.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerWidgetInterface.h>
namespace AzToolsFramework
{
@@ -72,42 +76,81 @@ namespace AzToolsFramework
bool InstanceUpdateExecutor::UpdateTemplateInstancesInQueue()
{
const int instanceCountToUpdateInBatch = m_instanceCountToUpdateInBatch == 0 ? m_instancesUpdateQueue.size() : m_instanceCountToUpdateInBatch;
TemplateId currentTemplateId = InvalidTemplateId;
TemplateReference currentTemplateReference = AZStd::nullopt;
bool isUpdateSuccessful = true;
for (int i = 0; i < instanceCountToUpdateInBatch; ++i)
if (!m_updatingTemplateInstancesInQueue)
{
Instance* instanceToUpdate = m_instancesUpdateQueue.front();
TemplateId instanceTemplateId = instanceToUpdate->GetTemplateId();
if (currentTemplateId != instanceTemplateId)
m_updatingTemplateInstancesInQueue = true;
const int instanceCountToUpdateInBatch =
m_instanceCountToUpdateInBatch == 0 ? m_instancesUpdateQueue.size() : m_instanceCountToUpdateInBatch;
TemplateId currentTemplateId = InvalidTemplateId;
TemplateReference currentTemplateReference = AZStd::nullopt;
if (instanceCountToUpdateInBatch > 0)
{
currentTemplateId = instanceTemplateId;
currentTemplateReference = m_prefabSystemComponentInterface->FindTemplate(currentTemplateId);
if (!currentTemplateReference.has_value())
EntityIdList selectedEntityIds;
ToolsApplicationRequestBus::BroadcastResult(selectedEntityIds, &ToolsApplicationRequests::GetSelectedEntities);
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, EntityIdList());
// Disable the Outliner to avoid showing the propagation steps
EntityOutlinerWidgetInterface* entityOutlinerWidgetInterface = AZ::Interface<EntityOutlinerWidgetInterface>::Get();
if (entityOutlinerWidgetInterface)
{
AZ_Error("Prefab", false,
"InstanceUpdateExecutor::UpdateTemplateInstancesInQueue - "
"Could not find Template using Id '%llu'. Unable to update Instance.",
currentTemplateId);
isUpdateSuccessful = false;
entityOutlinerWidgetInterface->SetUpdatesEnabled(false);
}
for (int i = 0; i < instanceCountToUpdateInBatch; ++i)
{
Instance* instanceToUpdate = m_instancesUpdateQueue.front();
TemplateId instanceTemplateId = instanceToUpdate->GetTemplateId();
if (currentTemplateId != instanceTemplateId)
{
currentTemplateId = instanceTemplateId;
currentTemplateReference = m_prefabSystemComponentInterface->FindTemplate(currentTemplateId);
if (!currentTemplateReference.has_value())
{
AZ_Error(
"Prefab", false,
"InstanceUpdateExecutor::UpdateTemplateInstancesInQueue - "
"Could not find Template using Id '%llu'. Unable to update Instance.",
currentTemplateId);
isUpdateSuccessful = false;
}
}
Template& currentTemplate = currentTemplateReference->get();
Instance::EntityList newEntities;
if (!PrefabDomUtils::LoadInstanceFromPrefabDom(
*instanceToUpdate, newEntities, currentTemplate.GetPrefabDom()))
{
AZ_Error(
"Prefab", false,
"InstanceUpdateExecutor::UpdateTemplateInstancesInQueue - "
"Could not load Instance from Prefab DOM of Template with Id '%llu' on file path '%s'.",
currentTemplateId, currentTemplate.GetFilePath().c_str());
isUpdateSuccessful = false;
}
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, newEntities);
m_instancesUpdateQueue.pop();
}
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, selectedEntityIds);
// Enable the Outliner
AZ::SystemTickBus::QueueFunction([entityOutlinerWidgetInterface]() {
if (entityOutlinerWidgetInterface)
{
entityOutlinerWidgetInterface->SetUpdatesEnabled(true);
}
});
}
Template& currentTemplate = currentTemplateReference->get();
if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*instanceToUpdate, currentTemplate.GetPrefabDom(), true))
{
AZ_Error("Prefab", false,
"InstanceUpdateExecutor::UpdateTemplateInstancesInQueue - "
"Could not load Instance from Prefab DOM of Template with Id '%llu' on file path '%s'.",
currentTemplateId, currentTemplate.GetFilePath().c_str());
isUpdateSuccessful = false;
}
m_instancesUpdateQueue.pop();
m_updatingTemplateInstancesInQueue = false;
}
return isUpdateSuccessful;
@@ -46,6 +46,7 @@ namespace AzToolsFramework
TemplateInstanceMapperInterface* m_templateInstanceMapperInterface = nullptr;
int m_instanceCountToUpdateInBatch = 0;
AZStd::queue<Instance*> m_instancesUpdateQueue;
bool m_updatingTemplateInstancesInQueue { false };
};
}
}
@@ -26,6 +26,7 @@ namespace AzToolsFramework
using PrefabDomPath = rapidjson::Pointer;
using PrefabDomList = AZStd::vector<PrefabDom>;
using PrefabDomReference = AZStd::optional<AZStd::reference_wrapper<PrefabDom>>;
using PrefabDomValueReference = AZStd::optional<AZStd::reference_wrapper<PrefabDomValue>>;
using PrefabDomValueConstReference = AZStd::optional<AZStd::reference_wrapper<const PrefabDomValue>>;
@@ -10,12 +10,17 @@
*
*/
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityScrubber.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
#include <AzToolsFramework/Prefab/Instance/InstanceSerializer.h>
#include <QDebug>
namespace AzToolsFramework
{
namespace Prefab
@@ -71,15 +76,21 @@ namespace AzToolsFramework
return true;
}
bool LoadInstanceFromPrefabDom(Instance& instance, const PrefabDom& prefabDom, bool shouldClearContainers)
bool LoadInstanceFromPrefabDom(Instance& instance, const PrefabDom& prefabDom, LoadInstanceFlags flags)
{
InstanceEntityIdMapper entityIdMapper;
entityIdMapper.SetLoadingInstance(instance);
if ((flags & LoadInstanceFlags::AssignRandomEntityId) == LoadInstanceFlags::AssignRandomEntityId)
{
entityIdMapper.SetEntityIdGenerationApproach(InstanceEntityIdMapper::EntityIdGenerationApproach::Random);
}
AZ::JsonDeserializerSettings settings;
// The InstanceEntityIdMapper is registered twice because it's used in several places during deserialization where one is
// specific for the InstanceEntityIdMapper and once for the generic JsonEntityIdMapper. Because the Json Serializer's meta
// data has strict typing and doesn't look for inheritance both have to be explicitly added so they're found both locations.
settings.m_metadata.Add(static_cast<AZ::JsonEntityIdSerializer::JsonEntityIdMapper*>(&entityIdMapper));
settings.m_metadata.Add(&entityIdMapper);
settings.m_clearContainers = shouldClearContainers;
AZ::JsonSerializationResult::ResultCode result =
AZ::JsonSerialization::Load(instance, prefabDom, settings);
@@ -93,12 +104,54 @@ namespace AzToolsFramework
return false;
}
entityIdMapper.FixUpUnresolvedEntityReferences();
return true;
}
bool LoadInstanceFromPrefabDom(
Instance& instance, Instance::EntityList& newlyAddedEntities, const PrefabDom& prefabDom, LoadInstanceFlags flags)
{
InstanceEntityIdMapper entityIdMapper;
entityIdMapper.SetLoadingInstance(instance);
if ((flags & LoadInstanceFlags::AssignRandomEntityId) == LoadInstanceFlags::AssignRandomEntityId)
{
entityIdMapper.SetEntityIdGenerationApproach(InstanceEntityIdMapper::EntityIdGenerationApproach::Random);
}
AZ::JsonDeserializerSettings settings;
// The InstanceEntityIdMapper is registered twice because it's used in several places during deserialization where one is
// specific for the InstanceEntityIdMapper and once for the generic JsonEntityIdMapper. Because the Json Serializer's meta
// data has strict typing and doesn't look for inheritance both have to be explicitly added so they're found both locations.
settings.m_metadata.Add(static_cast<AZ::JsonEntityIdSerializer::JsonEntityIdMapper*>(&entityIdMapper));
settings.m_metadata.Add(&entityIdMapper);
InstanceEntityScrubber instanceEntityScrubber(newlyAddedEntities);
settings.m_metadata.Add(&instanceEntityScrubber);
AZ::JsonSerializationResult::ResultCode result =
AZ::JsonSerialization::Load(instance, prefabDom, settings);
if (result.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
{
AZ_Error("Prefab", false,
"Failed to de-serialize Prefab Instance from Prefab DOM. "
"Unable to proceed.");
return false;
}
return true;
}
void PrintPrefabDomValue(
[[maybe_unused]] const AZStd::string_view printMessage,
[[maybe_unused]] const PrefabDomValue& prefabDomValue)
{
rapidjson::StringBuffer prefabBuffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(prefabBuffer);
prefabDomValue.Accept(writer);
qDebug() << printMessage.data() << "\n" << prefabBuffer.GetString();
}
} // namespace PrefabDomUtils
} // namespace Prefab
} // namespace AzToolsFramework
@@ -13,6 +13,7 @@
#pragma once
#include <AzCore/std/optional.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
namespace AzToolsFramework
@@ -27,6 +28,7 @@ namespace AzToolsFramework
inline static const char* SourceName = "Source";
inline static const char* LinkIdName = "LinkId";
inline static const char* EntitiesName = "Entities";
inline static const char* ContainerEntityName = "ContainerEntity";
/**
* Find Prefab value from given parent value and target value's name.
@@ -45,6 +47,16 @@ namespace AzToolsFramework
*/
bool StoreInstanceInPrefabDom(const Instance& instance, PrefabDom& prefabDom);
enum class LoadInstanceFlags : uint8_t
{
//! No flags used during the call to LoadInstanceFromPrefabDom.
None = 0,
//! By default entities will get a stable id when they're deserialized. In cases where the new entities need to be kept
//! unique, e.g. when they are duplicates of live entities, this flag will assign them a random new id.
AssignRandomEntityId = 1 << 0
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(LoadInstanceFlags)
/**
* Loads a valid Prefab Instance from a Prefab Dom. Useful for generating Instances.
* @param instance The Instance to load.
@@ -52,7 +64,21 @@ namespace AzToolsFramework
* @param shouldClearContainers whether to clear containers in Instance while loading.
* @return bool on whether the operation succeeded.
*/
bool LoadInstanceFromPrefabDom(Instance& instance, const PrefabDom& prefabDom, bool shouldClearContainers);
bool LoadInstanceFromPrefabDom(
Instance& instance, const PrefabDom& prefabDom, LoadInstanceFlags flags = LoadInstanceFlags::None);
/**
* Loads a valid Prefab Instance from a Prefab Dom. Useful for generating Instances.
* @param instance The Instance to load.
* @param newlyAddedEntities The new instances added during deserializing the instance. These are the entities found
* in the prefabDom.
* @param prefabDom the prefabDom that will be used to load the Instance data.
* @param shouldClearContainers whether to clear containers in Instance while loading.
* @return bool on whether the operation succeeded.
*/
bool LoadInstanceFromPrefabDom(
Instance& instance, Instance::EntityList& newlyAddedEntities, const PrefabDom& prefabDom,
LoadInstanceFlags flags = LoadInstanceFlags::None);
inline PrefabDomPath GetPrefabDomInstancePath(const char* instanceName)
{
@@ -61,6 +87,15 @@ namespace AzToolsFramework
.Append(instanceName);
};
/**
* Prints the contents of the given prefab DOM value to the debug output console in a readable format.
* @param printMessage The message that will be printed before printing the PrefabDomValue
* @param prefabDomValue The DOM value to be printed. A 'PrefabDom' type can also be passed into this variable.
*/
void PrintPrefabDomValue(
[[maybe_unused]] const AZStd::string_view printMessage,
[[maybe_unused]] const AzToolsFramework::Prefab::PrefabDomValue& prefabDomValue);
} // namespace PrefabDomUtils
} // namespace Prefab
} // namespace AzToolsFramework
@@ -10,10 +10,16 @@
*
*/
#include <AzCore/IO/Path/Path.h>
#include <AzToolsFramework/Prefab/PrefabLoader.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/FileFunc/FileFunc.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
@@ -30,6 +36,14 @@ namespace AzToolsFramework
"It is a requirement for the PrefabLoader class. "
"Check that it is being correctly initialized.");
auto settingsRegistry = AZ::SettingsRegistry::Get();
AZ_Assert(settingsRegistry, "Settings registry is not set");
bool result =
settingsRegistry->Get(m_projectPathWithOsSeparator.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
AZ_Assert(result, "Couldn't retrieve project root path");
m_projectPathWithSlashSeparator = AZ::IO::Path(m_projectPathWithOsSeparator.Native(), '/').MakePreferred();
AZ::Interface<PrefabLoaderInterface>::Register(this);
}
@@ -38,57 +52,114 @@ namespace AzToolsFramework
AZ::Interface<PrefabLoaderInterface>::Unregister(this);
}
TemplateId PrefabLoader::LoadTemplate(const AZStd::string& filePath)
TemplateId PrefabLoader::LoadTemplateFromFile(AZ::IO::PathView filePath)
{
AZStd::unordered_set<AZStd::string> progressedFilePathsSet;
TemplateId newTemplateId = LoadTemplate(filePath, progressedFilePathsSet);
AZStd::unordered_set<AZ::IO::Path> progressedFilePathsSet;
TemplateId newTemplateId = LoadTemplateFromFile(filePath, progressedFilePathsSet);
return newTemplateId;
}
TemplateId PrefabLoader::LoadTemplate(
const AZStd::string& filePath,
AZStd::unordered_set<AZStd::string>& progressedFilePathsSet)
TemplateId PrefabLoader::LoadTemplateFromFile(AZ::IO::PathView filePath, AZStd::unordered_set<AZ::IO::Path>& progressedFilePathsSet)
{
if (!IsValidPrefabPath(filePath))
{
AZ_Error(
"Prefab", false,
"PrefabLoader::LoadTemplateFromFile - "
"Invalid file path: '%.*s'.",
AZ_STRING_ARG(filePath.Native())
);
return InvalidTemplateId;
}
auto readResult = AZ::Utils::ReadFile(GetFullPath(filePath).Native(), MaxPrefabFileSize);
if (!readResult.IsSuccess())
{
AZ_Error(
"Prefab", false,
"PrefabLoader::LoadTemplate - Failed to load Prefab file from '%.*s'."
"Error message: '%s'",
AZ_STRING_ARG(filePath.Native()),
readResult.GetError().c_str()
);
return InvalidTemplateId;
}
return LoadTemplateFromString(readResult.GetValue(), filePath, progressedFilePathsSet);
}
TemplateId PrefabLoader::LoadTemplateFromString(
AZStd::string_view content, AZ::IO::PathView originPath)
{
AZStd::unordered_set<AZ::IO::Path> progressedFilePathsSet;
TemplateId newTemplateId = LoadTemplateFromString(content, originPath, progressedFilePathsSet);
return newTemplateId;
}
TemplateId PrefabLoader::LoadTemplateFromString(
AZStd::string_view fileContent,
AZ::IO::PathView originPath,
AZStd::unordered_set<AZ::IO::Path>& progressedFilePathsSet)
{
if (!IsValidPrefabPath(originPath))
{
AZ_Error(
"Prefab", false,
"PrefabLoader::LoadTemplateFromString - "
"Invalid origin path: '%.*s'",
AZ_STRING_ARG(originPath.Native())
);
return InvalidTemplateId;
}
AZ::IO::Path relativePath = GetRelativePathToProject(originPath);
// Cyclical dependency detected if the prefab file is already part of the progressed
// file path set.
if (progressedFilePathsSet.contains(filePath))
if (progressedFilePathsSet.contains(relativePath))
{
AZ_Error("Prefab", false,
"PrefabLoader::LoadTemplate - "
"Prefab file %s has been detected to directly or indirectly depend on itself."
AZ_Error(
"Prefab", false,
"PrefabLoader::LoadTemplateFromString - "
"Prefab file '%.*s' has been detected to directly or indirectly depend on itself."
"Terminating any further loading of this branch of its prefab hierarchy.",
filePath.c_str());
AZ_STRING_ARG(originPath.Native())
);
return InvalidTemplateId;
}
// Directly return loaded Template id.
TemplateId loadedTemplateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(filePath);
TemplateId loadedTemplateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath);
if (loadedTemplateId != InvalidTemplateId)
{
return loadedTemplateId;
}
// Read Template's prefab file from disk and parse Prefab DOM from file.
AZ::Outcome<PrefabDom, AZStd::string> readPrefabFileResult = AzFramework::FileFunc::ReadJsonFile(AZ::IO::Path(filePath));
AZ::Outcome<PrefabDom, AZStd::string> readPrefabFileResult = AzFramework::FileFunc::ReadJsonFromString(fileContent);
if (!readPrefabFileResult.IsSuccess())
{
AZ_Error("Prefab", false,
"PrefabLoader::LoadPrefabFile - Failed to load Prefab file from '%s'."
AZ_Error(
"Prefab", false,
"PrefabLoader::LoadTemplate - Failed to load Prefab file from '%.*s'."
"Error message: '%s'",
filePath.c_str(), readPrefabFileResult.GetError().c_str());
AZ_STRING_ARG(originPath.Native()),
readPrefabFileResult.GetError().c_str());
return InvalidTemplateId;
}
// Create new Template with the Prefab DOM.
TemplateId newTemplateId = m_prefabSystemComponentInterface->AddTemplate(filePath, readPrefabFileResult.TakeValue());
TemplateId newTemplateId = m_prefabSystemComponentInterface->AddTemplate(relativePath, readPrefabFileResult.TakeValue());
if (newTemplateId == InvalidTemplateId)
{
AZ_Error("Prefab", false,
AZ_Error(
"Prefab", false,
"PrefabLoader::LoadTemplate - "
"Failed to create a template from instance with source file path '%s': "
"Failed to create a template from instance with source file path '%.*s': "
"invalid template id returned.",
filePath.c_str());
AZ_STRING_ARG(originPath.Native())
);
return InvalidTemplateId;
}
@@ -97,7 +168,7 @@ namespace AzToolsFramework
Template& newTemplate = newTemplateReference->get();
// Mark the file as being in progress.
progressedFilePathsSet.emplace(filePath);
progressedFilePathsSet.emplace(relativePath);
// Get 'Instances' value from Template.
bool isLoadedWithErrors = false;
@@ -108,57 +179,58 @@ namespace AzToolsFramework
// For each instance value in 'instances', try to create source Templates for target Template's nested instance data.
// Also create Links between source/target Templates if source Template loaded successfully.
for (PrefabDomValue::MemberIterator instanceIterator = instances.MemberBegin(); instanceIterator != instances.MemberEnd(); ++instanceIterator)
for (PrefabDomValue::MemberIterator instanceIterator = instances.MemberBegin(); instanceIterator != instances.MemberEnd();
++instanceIterator)
{
const PrefabDomValue& instance = instanceIterator->value;
if (!LoadNestedInstance(instanceIterator, newTemplateId, progressedFilePathsSet))
{
isLoadedWithErrors = true;
AZ_Error("Prefab", false,
AZ_Error(
"Prefab", false,
"PrefabLoader::LoadTemplate - "
"Loading nested instance '%s' in target Template '%u' from Prefab file '%s' failed.",
instanceIterator->name.GetString(),
newTemplateId,
filePath.c_str());
"Loading nested instance '%s' in target Template '%u' from Prefab file '%.*s' failed.",
instanceIterator->name.GetString(), newTemplateId,
AZ_STRING_ARG(originPath.Native())
);
}
}
}
newTemplate.MarkAsLoadedWithErrors(isLoadedWithErrors);
// Un-mark the file as being in progress.
progressedFilePathsSet.erase(filePath);
progressedFilePathsSet.erase(originPath);
// Return target Template id.
return newTemplateId;
}
bool PrefabLoader::LoadNestedInstance(
PrefabDomValue::MemberIterator& instanceIterator,
TemplateId targetTemplateId,
AZStd::unordered_set<AZStd::string>& progressedFilePathsSet)
PrefabDomValue::MemberIterator& instanceIterator, TemplateId targetTemplateId,
AZStd::unordered_set<AZ::IO::Path>& progressedFilePathsSet)
{
const PrefabDomValue& instance = instanceIterator->value;
AZ::IO::PathView instancePath = AZStd::string_view(instanceIterator->name.GetString(), instanceIterator->name.GetStringLength());
if (instanceIterator->name.GetStringLength() == 0)
if (!IsValidPrefabPath(instancePath))
{
AZ_Error("Prefab", false,
AZ_Error(
"Prefab", false,
"PrefabLoader::LoadNestedInstance - "
"There's an Instance without a name in the target Template on file path '%s'.",
"There's an Instance with an invalid path '%s' in the target Template on file path '%s'.",
instanceIterator->name.GetString(),
m_prefabSystemComponentInterface->FindTemplate(targetTemplateId)->get().GetFilePath().c_str());
return false;
}
// Get source Template's path for getting nested instance data.
PrefabDomValueConstReference sourceReference =
PrefabDomUtils::FindPrefabDomValue(instance, PrefabDomUtils::SourceName);
if (!sourceReference.has_value() ||
!sourceReference->get().IsString() ||
sourceReference->get().GetStringLength() == 0)
PrefabDomValueConstReference sourceReference = PrefabDomUtils::FindPrefabDomValue(instance, PrefabDomUtils::SourceName);
if (!sourceReference.has_value() || !sourceReference->get().IsString() || sourceReference->get().GetStringLength() == 0)
{
AZ_Error("Prefab", false,
AZ_Error(
"Prefab", false,
"PrefabLoader::LoadNestedInstance - "
"Can't get '%s' string value in Instance value '%s'of Template's Prefab DOM from file '%s'.",
"Can't get '%s' string value in Instance value '%s' of Template's Prefab DOM from file '%s'.",
PrefabDomUtils::SourceName, instanceIterator->name.GetString(),
m_prefabSystemComponentInterface->FindTemplate(targetTemplateId)->get().GetFilePath().c_str());
return false;
@@ -167,31 +239,36 @@ namespace AzToolsFramework
AZStd::string_view nestedTemplatePath(source.GetString(), source.GetStringLength());
// Get Template id of nested instance from its path.
// If source Template is already loaded, get the id from Template File Path To Id Map,
// If source Template is already loaded, get the id from Template File Path To Id Map,
// else load the source Template by calling 'LoadTemplate' recursively.
TemplateId nestedTemplateId = LoadTemplate(nestedTemplatePath, progressedFilePathsSet);
TemplateId nestedTemplateId = LoadTemplateFromFile(nestedTemplatePath, progressedFilePathsSet);
TemplateReference nestedTemplateReference = m_prefabSystemComponentInterface->FindTemplate(nestedTemplateId);
if (!nestedTemplateReference.has_value() ||
!nestedTemplateReference->get().IsValid())
if (!nestedTemplateReference.has_value() || !nestedTemplateReference->get().IsValid())
{
AZ_Error("Prefab", false,
AZ_Error(
"Prefab", false,
"PrefabLoader::LoadNestedInstance - "
"Error occurred while loading nested Prefab file '%.*s' from Prefab file '%s'.",
aznumeric_cast<int>(nestedTemplatePath.size()), nestedTemplatePath.data(),
m_prefabSystemComponentInterface->FindTemplate(targetTemplateId)->get().GetFilePath().c_str());
AZ_STRING_ARG(nestedTemplatePath),
m_prefabSystemComponentInterface->FindTemplate(targetTemplateId)->get().GetFilePath().c_str()
);
return false;
}
// After source template has been loaded, create Link between source/target Template.
LinkId newLinkId = m_prefabSystemComponentInterface->AddLink(nestedTemplateId, targetTemplateId, instanceIterator, AZStd::nullopt);
LinkId newLinkId =
m_prefabSystemComponentInterface->AddLink(nestedTemplateId, targetTemplateId, instanceIterator, AZStd::nullopt);
if (newLinkId == InvalidLinkId)
{
AZ_Error("Prefab", false,
AZ_Error(
"Prefab", false,
"PrefabLoader::LoadNestedInstance - "
"Failed to add a new Link to Nested Template Instance '%s' which connects source Template '%.*s' and target Template '%s'.",
instanceIterator->name.GetString(),
aznumeric_cast<int>(nestedTemplatePath.size()), nestedTemplatePath.data(),
m_prefabSystemComponentInterface->FindTemplate(targetTemplateId)->get().GetFilePath().c_str());
"Failed to add a new Link to Nested Template Instance '%s' which connects source Template '%.*s' and target Template "
"'%s'.",
instanceIterator->name.GetString(), AZ_STRING_ARG(nestedTemplatePath),
m_prefabSystemComponentInterface->FindTemplate(targetTemplateId)->get().GetFilePath().c_str()
);
return false;
}
@@ -200,45 +277,136 @@ namespace AzToolsFramework
return !nestedTemplateReference->get().IsLoadedWithErrors();
}
bool PrefabLoader::SaveTemplate(const TemplateId& templateId)
bool PrefabLoader::SaveTemplate(TemplateId templateId)
{
const auto& domAndFilepath = StoreTemplateIntoFileFormat(templateId);
if (!domAndFilepath)
{
return false;
}
auto outcome = AzFramework::FileFunc::WriteJsonFile(domAndFilepath->first, GetFullPath(domAndFilepath->second));
if (!outcome.IsSuccess())
{
AZ_Error(
"Prefab", false,
"PrefabLoader::SaveTemplate - "
"Failed to save template '%s'."
"Error: %s",
domAndFilepath->second.c_str(),
outcome.GetError().c_str()
);
return false;
}
m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, false);
return true;
}
bool PrefabLoader::SaveTemplateToString(TemplateId templateId, AZStd::string& output)
{
const auto& domAndFilepath = StoreTemplateIntoFileFormat(templateId);
if (!domAndFilepath)
{
return false;
}
auto outcome = AzFramework::FileFunc::WriteJsonToString(domAndFilepath->first, output);
if (!outcome.IsSuccess())
{
AZ_Error(
"Prefab", false,
"PrefabLoader::SaveTemplateToString - "
"Failed to serialize template '%s' into a string."
"Error: %s",
domAndFilepath->second.c_str(),
outcome.GetError().c_str()
);
return false;
}
return true;
}
AZStd::optional<AZStd::pair<PrefabDom, AZ::IO::Path>> PrefabLoader::StoreTemplateIntoFileFormat(TemplateId templateId)
{
// Acquire the template we are saving
TemplateReference templateToSaveReference = m_prefabSystemComponentInterface->FindTemplate(templateId);
if (!templateToSaveReference.has_value())
{
AZ_Warning("Prefab", false,
AZ_Warning(
"Prefab", false,
"PrefabLoader::SaveTemplate - Unable to save prefab template with id: '%llu'. "
"Template with that id could not be found",
templateId);
templateId
);
return false;
return AZStd::nullopt;
}
Template& templateToSave = templateToSaveReference->get();
if (!templateToSave.IsValid())
{
AZ_Warning("Prefab", false,
AZ_Warning(
"Prefab", false,
"PrefabLoader::SaveTemplate - Unable to save Prefab Template with id: %llu. "
"Template with that id is invalid",
templateId);
templateId
);
return false;
return AZStd::nullopt;
}
// Make a copy of a our prefab DOM where nested instances become file references with patch data
PrefabDom templateDomToSave;
if (!templateToSave.CopyTemplateIntoPrefabFileFormat(templateDomToSave))
{
AZ_Error("Prefab", false,
"PrefabLoader::SaveTemplate - Unable to store a collapsed version of prefab Template while attempting to save to %s"
AZ_Error(
"Prefab", false,
"PrefabLoader::SaveTemplate - Unable to store a collapsed version of prefab Template while attempting to save to %s."
"Save cannot continue",
templateToSave.GetFilePath().c_str());
templateToSave.GetFilePath().c_str()
);
return false;
return AZStd::nullopt;
}
// Save the file
return AzFramework::FileFunc::WriteJsonFile(templateDomToSave, templateToSave.GetFilePath()).IsSuccess();
return { { AZStd::move(templateDomToSave), templateToSave.GetFilePath() } };
}
bool PrefabLoader::IsValidPrefabPath(AZ::IO::PathView path)
{
// Check for OS invalid character and paths ending on '/' '\\' separators as final char
AZStd::string_view pathStr = path.Native();
return !path.empty() &&
(pathStr.find_first_of(AZ_FILESYSTEM_INVALID_CHARACTERS) == AZStd::string::npos) &&
(pathStr.back() != '\\' && pathStr.back() != '/');
}
AZ::IO::Path PrefabLoader::GetFullPath(AZ::IO::PathView path)
{
AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path).MakePreferred();
if (pathWithOSSeparator.IsAbsolute())
{
return path;
}
return AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator);
}
AZ::IO::Path PrefabLoader::GetRelativePathToProject(AZ::IO::PathView path)
{
AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path.Native()).MakePreferred();
if (!pathWithOSSeparator.IsAbsolute())
{
return path;
}
return AZ::IO::Path(path.Native(), '/').MakePreferred().LexicallyRelative(m_projectPathWithSlashSeparator);
}
AZ::IO::Path PrefabLoaderInterface::GeneratePath()
{
return AZStd::string::format("Prefab_%s", AZ::Entity::MakeId().ToString().c_str());
}
} // namespace Prefab
@@ -14,15 +14,19 @@
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/string/string.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
namespace AzToolsFramework
namespace AZ
{
class Entity;
} // namespace AZ
namespace AzToolsFramework
{
namespace Prefab
{
class PrefabSystemComponentInterface;
@@ -42,37 +46,81 @@ namespace AzToolsFramework
/**
* Load Prefab Template from given file path to memory and return the id of loaded Template.
* Converts Prefab Asset form into Prefab Template form by expanding source path and patch info
* Converts .prefab into Prefab Template form by expanding source path and patch info
* into fully formed nested template info.
* @param filePath A Prefab Template file path.
* @return A unique id of Template on filePath loaded. Return null id if loading Template on filePath failed.
* @return A unique id of Template on filePath loaded. Return invalid template id if loading Template on filePath failed.
*/
TemplateId LoadTemplate(const AZStd::string& filePath) override;
TemplateId LoadTemplateFromFile(AZ::IO::PathView filePath) override;
/**
* Saves a Prefab Template to the the source path registered with the template.
* Converts Prefab Template form into Prefab Asset form by collapsing nested Template info
* into a source path and patches.
* @param templateId Id of the template to be saved
* @return bool on whether the operation succeeded or not
*/
bool SaveTemplate(const TemplateId& templateId) override;
* Load Prefab Template from given content string to memory and return the id of loaded Template.
* Converts .prefab form into Prefab Template form by expanding source path and patch info
* into fully formed nested template info.
* @param content Json content of the prefab
* @param originPath Path that will be used for the prefab in case of saved into a file.
* @return A unique id of Template on filePath loaded. Return invalid template id if loading Template on filePath failed.
*/
TemplateId LoadTemplateFromString(AZStd::string_view content, AZ::IO::PathView originPath = GeneratePath()) override;
/**
* Saves a Prefab Template to the the source path registered with the template.
* Converts Prefab Template form into .prefab form by collapsing nested Template info
* into a source path and patches.
* @param templateId Id of the template to be saved
* @return bool on whether the operation succeeded or not
*/
bool SaveTemplate(TemplateId templateId) override;
/**
* Saves a Prefab Template into the provided output string.
* Converts Prefab Template form into .prefab form by collapsing nested Template info
* into a source path and patches.
* @param templateId Id of the template to be saved
* @param outputJson Will contain the serialized template json on success
* @return bool on whether the operation succeeded or not
*/
bool SaveTemplateToString(TemplateId templateId, AZStd::string& outputJson) override;
//////////////////////////////////////////////////////////////////////////
void RegisterPrefabLoaderInterface();
void UnregisterPrefabLoaderInterface();
//! Converts path into full absolute path. This will be used by loading/save IO operations.
//! The path will always have the correct separator for the current OS
AZ::IO::Path GetFullPath(AZ::IO::PathView path) override;
//! Converts path into a relative path to the project, this will be the paths in .prefab file.
//! The path will always have '/' separator.
AZ::IO::Path GetRelativePathToProject(AZ::IO::PathView path) override;
//! Returns if the path is a valid path for a prefab
static bool IsValidPrefabPath(AZ::IO::PathView path);
private:
/**
* Load Prefab Template from given file path to memory and return the id of loaded Template.
* @param filePath A path to a Prefab Template file.
* @param progressedFilePathsSet An unordered_set to track if there's any cyclical dependency between Templates.
* @return A unique id of Template on filePath loaded. Return invalid id if loading Template on filePath failed.
*/
TemplateId LoadTemplate(
const AZStd::string& filePath,
AZStd::unordered_set<AZStd::string>& progressedFilePathsSet);
TemplateId LoadTemplateFromFile(
AZ::IO::PathView filePath,
AZStd::unordered_set<AZ::IO::Path>& progressedFilePathsSet);
/**
* Load Prefab Template from given string to memory and return the id of loaded Template.
* @param fileContent Json content of the template
* @param filePath Path that will be used for the template if saved to file.
* @param progressedFilePathsSet An unordered_set to track if there's any cyclical dependency between Templates.
* @return A unique id of Template on filePath loaded. Return invalid id if loading Template on filePath failed.
*/
TemplateId LoadTemplateFromString(
AZStd::string_view fileContent,
AZ::IO::PathView filePath,
AZStd::unordered_set<AZ::IO::Path>& progressedFilePathsSet);
/**
* Load nested instance given a nested instance value iterator and target Template with its id.
@@ -84,9 +132,14 @@ namespace AzToolsFramework
bool LoadNestedInstance(
PrefabDomValue::MemberIterator& instanceIterator,
TemplateId targetTemplateId,
AZStd::unordered_set<AZStd::string>& progressedFilePathsSet);
AZStd::unordered_set<AZ::IO::Path>& progressedFilePathsSet);
//! Retrieves Dom content and its path from a template id
AZStd::optional<AZStd::pair<PrefabDom, AZ::IO::Path>> StoreTemplateIntoFileFormat(TemplateId templateId);
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
AZ::IO::Path m_projectPathWithOsSeparator;
AZ::IO::Path m_projectPathWithSlashSeparator;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -13,6 +13,7 @@
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
@@ -20,6 +21,8 @@ namespace AzToolsFramework
{
namespace Prefab
{
constexpr size_t MaxPrefabFileSize = 1024 * 1024;
/*!
* PrefabLoaderInterface
* Interface for saving/loading Prefab files.
@@ -34,9 +37,19 @@ namespace AzToolsFramework
* Converts Prefab Asset form into Prefab Template form by expanding source path and patch info
* into fully formed nested template info.
* @param filePath A Prefab Template file path.
* @return A unique id of Template on filePath loaded. Return null id if loading Template on filePath failed.
* @return A unique id of Template on filePath loaded. Return invalid template id if loading Template on filePath failed.
*/
virtual TemplateId LoadTemplate(const AZStd::string& filePath) = 0;
virtual TemplateId LoadTemplateFromFile(AZ::IO::PathView filePath) = 0;
/**
* Load Prefab Template from given content string to memory and return the id of loaded Template.
* Converts .prefab form into Prefab Template form by expanding source path and patch info
* into fully formed nested template info.
* @param content Json content of the prefab
* @param originPath Path that will be used for the prefab in case of saved into a file.
* @return A unique id of Template on filePath loaded. Return invalid template id if loading Template on filePath failed.
*/
virtual TemplateId LoadTemplateFromString(AZStd::string_view content, AZ::IO::PathView originPath = GeneratePath()) = 0;
/**
* Saves a Prefab Template to the the source path registered with the template.
@@ -45,9 +58,31 @@ namespace AzToolsFramework
* @param templateId Id of the template to be saved
* @return bool on whether the operation succeeded or not
*/
virtual bool SaveTemplate(const TemplateId& templateId) = 0;
};
virtual bool SaveTemplate(TemplateId templateId) = 0;
/**
* Saves a Prefab Template into the provided output string.
* Converts Prefab Template form into .prefab form by collapsing nested Template info
* into a source path and patches.
* @param templateId Id of the template to be saved
* @param outputJson Will contain the serialized template json on success
* @return bool on whether the operation succeeded or not
*/
virtual bool SaveTemplateToString(TemplateId templateId, AZStd::string& outputJson) = 0;
//! Converts path into full absolute path. This will be used by loading/save IO operations.
//! The path will always have the correct separator for the current OS
virtual AZ::IO::Path GetFullPath(AZ::IO::PathView path) = 0;
//! Converts path into a relative path to the current project, this will be the paths in .prefab file.
//! The path will always have '/' separator.
virtual AZ::IO::Path GetRelativePathToProject(AZ::IO::PathView path) = 0;
protected:
// Generates a new path
static AZ::IO::Path GeneratePath();
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -13,46 +13,55 @@
#include <AzToolsFramework/Prefab/PrefabPublicHandler.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Utils/TypeHash.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/PrefabUndo.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
namespace AzToolsFramework
{
namespace Prefab
{
PrefabPublicHandler::PrefabPublicHandler()
void PrefabPublicHandler::RegisterPrefabPublicHandlerInterface()
{
m_instanceEntityMapperInterface = AZ::Interface<InstanceEntityMapperInterface>::Get();
AZ_Assert(m_instanceEntityMapperInterface, "PrefabPublicHandler - Could not retrieve instance of InstanceEntityMapperInterface");
AZ_Assert(
m_instanceEntityMapperInterface, "PrefabPublicHandler - Could not retrieve instance of InstanceEntityMapperInterface");
m_instanceToTemplateInterface = AZ::Interface<InstanceToTemplateInterface>::Get();
AZ_Assert(m_instanceToTemplateInterface, "PrefabPublicHandler - Could not retrieve instance of InstanceToTemplateInterface");
m_prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
AZ_Assert(m_prefabSystemComponentInterface, "Could not get PrefabSystemComponentInterface on PrefabPublicHandler construction.");
m_prefabUndoCache.Initialize();
AZ::Interface<PrefabPublicInterface>::Register(this);
}
PrefabPublicHandler::~PrefabPublicHandler()
void PrefabPublicHandler::UnregisterPrefabPublicHandlerInterface()
{
AZ::Interface<PrefabPublicInterface>::Unregister(this);
m_prefabUndoCache.Destroy();
}
PrefabOperationResult PrefabPublicHandler::CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath)
{
// Retrieve entityList from entityIds
EntityList inputEntityList;
inputEntityList.reserve(entityIds.size());
for (AZ::EntityId entityId : entityIds)
{
if (entityId.IsValid())
{
inputEntityList.emplace_back(GetEntityById(entityId));
}
}
EntityIdListToEntityList(entityIds, inputEntityList);
// Find common root and top level entities
bool entitiesHaveCommonRoot = false;
@@ -80,7 +89,7 @@ namespace AzToolsFramework
}
// Retrieve the owning instance of the common root entity, which will be our new instance's parent instance.
InstanceOptionalReference commonRootEntityOwningInstance = GetCommonRootEntityOwningInstance(commonRootEntityId);
InstanceOptionalReference commonRootEntityOwningInstance = GetOwnerInstanceByEntityId(commonRootEntityId);
AZ_Assert(commonRootEntityOwningInstance.has_value(), "Failed to create prefab : "
"Couldn't get a valid owning instance for the common root entity of the enities provided");
@@ -101,7 +110,7 @@ namespace AzToolsFramework
}
InstanceOptionalReference instance = prefabEditorEntityOwnershipInterface->CreatePrefab(
entities, AZStd::move(instances), filePath, commonRootEntityOwningInstance->get());
entities, AZStd::move(instances), filePath, commonRootEntityOwningInstance);
if (!instance)
{
@@ -109,12 +118,6 @@ namespace AzToolsFramework
"(A null instance is returned)."));
}
auto prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
if (prefabLoaderInterface == nullptr)
{
return AZ::Failure(AZStd::string("Could not create a new prefab out of the entities provided - internal error (PrefabLoaderInterface unavailable)."));
}
AZ::EntityId containerEntityId = instance->get().GetContainerEntityId();
AZ::Vector3 containerEntityTranslation(AZ::Vector3::CreateZero());
AZ::Quaternion containerEntityRotation(AZ::Quaternion::CreateZero());
@@ -128,23 +131,18 @@ namespace AzToolsFramework
// Set container entity to be child of common root
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, commonRootEntityId);
// Assign the EditorPrefabComponent to the instance container
EntityCompositionRequests::AddComponentsOutcome outcome;
EntityCompositionRequestBus::BroadcastResult(
outcome, &EntityCompositionRequests::AddComponentsToEntities, EntityIdList{containerEntityId},
AZ::ComponentTypeList{azrtti_typeid<AzToolsFramework::Prefab::EditorPrefabComponent>()});
// Change top level entities to be parented to the container entity
for (AZ::Entity* topLevelEntity : topLevelEntities)
{
AZ::TransformBus::Event(topLevelEntity->GetId(), &AZ::TransformBus::Events::SetParent, containerEntityId);
}
// Register container entity to PrefabUiHandler
auto editorEntityUiInterface = AZ::Interface<EditorEntityUiInterface>::Get();
if (editorEntityUiInterface != nullptr)
{
editorEntityUiInterface->RegisterEntity(containerEntityId, 1);
}
// Save Template
prefabLoaderInterface->SaveTemplate(instance->get().GetTemplateId());
return AZ::Success();
}
@@ -153,25 +151,391 @@ namespace AzToolsFramework
return AZ::Failure(AZStd::string("Prefab - InstantiatePrefab is yet to be implemented."));
}
bool PrefabPublicHandler::IsInstanceContainerEntity(AZ::EntityId entityId)
PrefabOperationResult PrefabPublicHandler::SavePrefab(AZ::IO::Path filePath)
{
AZ::Entity* entity = GetEntityById(entityId);
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entity->GetId());
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
if (!prefabSystemComponentInterface)
{
AZ_Assert(
false,
"Prefab - PrefabPublicHandler - "
"Prefab System Component Interface could not be found. "
"Check that it is being correctly initialized.");
return AZ::Failure(
AZStd::string("SavePrefab - Internal error (Prefab System Component Interface could not be found)."));
}
auto templateId = prefabSystemComponentInterface->GetTemplateIdFromFilePath(filePath.c_str());
if (templateId == InvalidTemplateId)
{
return AZ::Failure(
AZStd::string("SavePrefab - Path error. Path could be invalid, or the prefab may not be loaded in this level."));
}
auto prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
if (prefabLoaderInterface == nullptr)
{
return AZ::Failure(AZStd::string(
"Could not save prefab - internal error (PrefabLoaderInterface unavailable)."));
}
if (!prefabLoaderInterface->SaveTemplate(templateId))
{
return AZ::Failure(AZStd::string("Could not save prefab - internal error (Json write operation failure)."));
}
return AZ::Success();
}
PrefabEntityResult PrefabPublicHandler::CreateEntity(AZ::EntityId parentId, const AZ::Vector3& position)
{
InstanceOptionalReference owningInstanceOfParentEntity = GetOwnerInstanceByEntityId(parentId);
if (!owningInstanceOfParentEntity)
{
return AZ::Failure(AZStd::string::format(
"Cannot add entity because the owning instance of parent entity with id '%llu' could not be found.",
static_cast<AZ::u64>(parentId)));
}
EntityAlias entityAlias = Instance::GenerateEntityAlias();
AliasPath absoluteEntityPath = owningInstanceOfParentEntity->get().GetAbsoluteInstanceAliasPath();
absoluteEntityPath.Append(entityAlias);
AZ::EntityId entityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath);
AZStd::string entityName = AZStd::string::format("Entity%llu", static_cast<AZ::u64>(m_newEntityCounter++));
AZ::Entity* entity = aznew AZ::Entity(entityId, entityName.c_str());
Instance& entityOwningInstance = owningInstanceOfParentEntity->get();
PrefabDom instanceDomBeforeUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBeforeUpdate, entityOwningInstance);
ScopedUndoBatch undoBatch("Add Entity");
entityOwningInstance.AddEntity(*entity, entityAlias);
EditorEntityContextRequestBus::Broadcast(&EditorEntityContextRequestBus::Events::HandleEntitiesAdded, EntityList{entity});
AZ::Transform transform = AZ::Transform::CreateIdentity();
transform.SetTranslation(position);
EntityOptionalReference owningInstanceContainerEntity = entityOwningInstance.GetContainerEntity();
if (owningInstanceContainerEntity && !parentId.IsValid())
{
parentId = owningInstanceContainerEntity->get().GetId();
}
if (parentId.IsValid())
{
AZ::TransformBus::Event(entityId, &AZ::TransformInterface::SetParent, parentId);
AZ::TransformBus::Event(entityId, &AZ::TransformInterface::SetLocalTM, transform);
}
else
{
AZ::TransformBus::Event(entityId, &AZ::TransformInterface::SetWorldTM, transform);
}
// Select the new entity (and deselect others).
AzToolsFramework::EntityIdList selection = {entityId};
SelectionCommand* selectionCommand = aznew SelectionCommand(selection, "");
selectionCommand->SetParent(undoBatch.GetUndoBatch());
ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, selection);
PrefabDom instanceDomAfterUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfterUpdate, entityOwningInstance);
// Generate the patch comparing the instance before and after the entity addition.
PrefabDom patch;
if (!m_instanceToTemplateInterface->GeneratePatch(patch, instanceDomBeforeUpdate, instanceDomAfterUpdate))
{
return AZ::Failure(AZStd::string::format(
"A valid patch couldn't be created for adding an entity with id '%llu'", static_cast<AZ::u64>(entityId)));
}
// create undo node
PrefabUndoInstance* state = aznew PrefabUndoInstance(AZStd::string::format("%llu", static_cast<AZ::u64>(entityId)));
state->Capture(instanceDomBeforeUpdate, instanceDomAfterUpdate, entityOwningInstance.GetTemplateId());
state->SetParent(undoBatch.GetUndoBatch());
state->Redo();
return AZ::Success(entityId);
}
void PrefabPublicHandler::GenerateUndoNodesForEntityChangeAndUpdateCache(
AZ::EntityId entityId, UndoSystem::URSequencePoint* parentUndoBatch)
{
// Create Undo node on entities if they belong to an instance
InstanceOptionalReference instanceOptionalReference = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
if (instanceOptionalReference.has_value())
{
PrefabDom beforeState;
m_prefabUndoCache.Retrieve(entityId, beforeState);
PrefabDom afterState;
AZ::Entity* entity = GetEntityById(entityId);
if (entity)
{
m_instanceToTemplateInterface->GenerateDomForEntity(afterState, *entity);
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, beforeState, afterState);
if (patch.IsArray() && !patch.Empty() && beforeState.IsObject())
{
// Update the state of the entity
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
state->SetParent(parentUndoBatch);
state->Capture(beforeState, afterState, entityId);
state->Redo();
}
// Update the cache
m_prefabUndoCache.Store(entityId, AZStd::move(afterState));
}
}
}
bool PrefabPublicHandler::IsInstanceContainerEntity(AZ::EntityId entityId) const
{
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
return owningInstance && (owningInstance->get().GetContainerEntityId() == entityId);
}
AZ::EntityId PrefabPublicHandler::GetInstanceContainerEntityId(AZ::EntityId entityId)
bool PrefabPublicHandler::IsLevelInstanceContainerEntity(AZ::EntityId entityId) const
{
// Get owning instance
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
// Get level root instance
auto prefabEditorEntityOwnershipInterface = AZ::Interface<PrefabEditorEntityOwnershipInterface>::Get();
if (!prefabEditorEntityOwnershipInterface)
{
AZ_Assert(
false,
"Could not get owning instance of common root entity :"
"PrefabEditorEntityOwnershipInterface unavailable.");
}
InstanceOptionalReference levelInstance = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance();
return owningInstance
&& levelInstance
&& (&owningInstance->get() == &levelInstance->get())
&& (owningInstance->get().GetContainerEntityId() == entityId);
}
AZ::EntityId PrefabPublicHandler::GetInstanceContainerEntityId(AZ::EntityId entityId) const
{
AZ::Entity* entity = GetEntityById(entityId);
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entity->GetId());
if (owningInstance)
if (entity)
{
return owningInstance->get().GetContainerEntityId();
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entity->GetId());
if (owningInstance)
{
return owningInstance->get().GetContainerEntityId();
}
}
return AZ::EntityId();
}
AZ::EntityId PrefabPublicHandler::GetLevelInstanceContainerEntityId() const
{
auto prefabEditorEntityOwnershipInterface = AZ::Interface<PrefabEditorEntityOwnershipInterface>::Get();
if (!prefabEditorEntityOwnershipInterface)
{
AZ_Assert(
false,
"Could not get owning instance of common root entity :"
"PrefabEditorEntityOwnershipInterface unavailable.");
return AZ::EntityId();
}
auto rootInstance = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance();
if (!rootInstance.has_value())
{
return AZ::EntityId();
}
return rootInstance->get().GetContainerEntityId();
}
AZ::IO::Path PrefabPublicHandler::GetOwningInstancePrefabPath(AZ::EntityId entityId) const
{
AZ::IO::Path path;
InstanceOptionalReference instance = GetOwnerInstanceByEntityId(entityId);
if (instance.has_value())
{
path = instance->get().GetTemplateSourcePath();
}
return path;
}
PrefabRequestResult PrefabPublicHandler::HasUnsavedChanges(AZ::IO::Path prefabFilePath) const
{
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
if (!prefabSystemComponentInterface)
{
AZ_Assert(
false,
"Prefab - PrefabPublicHandler - "
"Prefab System Component Interface could not be found. "
"Check that it is being correctly initialized.");
return AZ::Failure(
AZStd::string("HasUnsavedChanges - Internal error (Prefab System Component Interface could not be found)."));
}
auto templateId = prefabSystemComponentInterface->GetTemplateIdFromFilePath(prefabFilePath.c_str());
if (templateId == InvalidTemplateId)
{
return AZ::Failure(AZStd::string("HasUnsavedChanges - Path error. Path could be invalid, or the prefab may not be loaded in this level."));
}
return AZ::Success(prefabSystemComponentInterface->IsTemplateDirty(templateId));
}
PrefabOperationResult PrefabPublicHandler::DeleteEntitiesInInstance(const EntityIdList& entityIds)
{
return DeleteFromInstance(entityIds, false);
}
PrefabOperationResult PrefabPublicHandler::DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds)
{
return DeleteFromInstance(entityIds, true);
}
PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
{
if (entityIds.empty())
{
return AZ::Success();
}
if (!EntitiesBelongToSameInstance(entityIds))
{
return AZ::Failure(AZStd::string("DeleteEntitiesAndAllDescendantsInInstance - Deletion Error. Cannot delete multiple "
"entities belonging to different instances with one operation."));
}
InstanceOptionalReference instance = GetOwnerInstanceByEntityId(entityIds[0]);
// Retrieve entityList from entityIds
EntityList inputEntityList;
EntityIdListToEntityList(entityIds, inputEntityList);
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
UndoSystem::URSequencePoint* currentUndoBatch = nullptr;
ToolsApplicationRequests::Bus::BroadcastResult(currentUndoBatch, &ToolsApplicationRequests::Bus::Events::GetCurrentUndoBatch);
bool createdUndo = false;
if (!currentUndoBatch)
{
createdUndo = true;
ToolsApplicationRequests::Bus::BroadcastResult(
currentUndoBatch, &ToolsApplicationRequests::Bus::Events::BeginUndoBatch, "Delete Selected");
AZ_Assert(currentUndoBatch, "Failed to create new undo batch.");
}
// In order to undo DeleteSelected, we have to create a selection command which selects the current selection
// and then add the deletion as children.
// Commands always execute themselves first and then their children (when going forwards)
// and do the opposite when going backwards.
EntityIdList selectedEntities;
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
SelectionCommand* selCommand = aznew SelectionCommand(selectedEntities, "Delete Entities");
// We insert a "deselect all" command before we delete the entities. This ensures the delete operations aren't changing
// selection state, which triggers expensive UI updates. By deselecting up front, we are able to do those expensive
// UI updates once at the start instead of once for each entity.
{
EntityIdList deselection;
SelectionCommand* deselectAllCommand = aznew SelectionCommand(deselection, "Deselect Entities");
deselectAllCommand->SetParent(selCommand);
}
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DeleteEntities:UndoCaptureAndPurgeEntities");
Prefab::PrefabDom instanceDomBefore;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, instance->get());
if (deleteDescendants)
{
AZStd::vector<AZ::Entity*> entities;
AZStd::vector<AZStd::unique_ptr<Instance>> instances;
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, instance->get(), entities, instances);
if (!success)
{
return AZ::Failure(AZStd::string("DeleteEntitiesAndAllDescendantsInInstance"));
}
for (AZ::Entity* entity : entities)
{
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entity->GetId());
}
for (auto& nestedInstance : instances)
{
nestedInstance.reset();
}
}
else
{
for (AZ::EntityId entityId : entityIds)
{
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
// If this is the container entity, it actually represents the instance so get its owner
if (owningInstance->get().GetContainerEntityId() == entityId)
{
auto instancePtr = instance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
instancePtr.reset();
}
else
{
instance->get().DetachEntity(entityId);
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entityId);
}
}
}
Prefab::PrefabDom instanceDomAfter;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, instance->get());
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance deletion");
command->Capture(instanceDomBefore, instanceDomAfter, instance->get().GetTemplateId());
command->SetParent(selCommand);
}
selCommand->SetParent(currentUndoBatch);
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DeleteEntities:RunRedo");
selCommand->RunRedo();
}
if (createdUndo)
{
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::EndUndoBatch);
}
return AZ::Success();
}
void PrefabPublicHandler::GenerateContainerEntityTransform(const EntityList& topLevelEntities,
AZ::Vector3& translation, AZ::Quaternion& rotation)
{
@@ -231,19 +595,19 @@ namespace AzToolsFramework
}
}
InstanceOptionalReference PrefabPublicHandler::GetCommonRootEntityOwningInstance(AZ::EntityId entityId)
InstanceOptionalReference PrefabPublicHandler::GetOwnerInstanceByEntityId(AZ::EntityId entityId) const
{
if (entityId.IsValid())
{
return m_instanceEntityMapperInterface->FindOwningInstance(entityId);
}
// If the commonRootEntity is invalid, then the owning instance would be the root prefab instance of the
// If the entityId is invalid, then the owning instance would be the root prefab instance of the
// PrefabEditorEntityOwnershipService.
auto prefabEditorEntityOwnershipInterface = AZ::Interface<PrefabEditorEntityOwnershipInterface>::Get();
if (!prefabEditorEntityOwnershipInterface)
{
AZ_Assert(false, "Could not get owining instance of common root entity :"
AZ_Assert(false, "Could not get owning instance of common root entity :"
"PrefabEditorEntityOwnershipInterface unavailable.");
}
return prefabEditorEntityOwnershipInterface->GetRootPrefabInstance();
@@ -368,5 +732,52 @@ namespace AzToolsFramework
return true;
}
bool PrefabPublicHandler::EntitiesBelongToSameInstance(const EntityIdList& entityIds) const
{
if (entityIds.size() <= 1)
{
return true;
}
InstanceOptionalReference sharedInstance = AZStd::nullopt;
for (AZ::EntityId entityId : entityIds)
{
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
// If this is the container entity, it actually represents the instance so get its owner
if (owningInstance->get().GetContainerEntityId() == entityId)
{
owningInstance = owningInstance->get().GetParentInstance();
}
if (!sharedInstance.has_value())
{
sharedInstance = owningInstance;
}
else
{
if (&sharedInstance->get() != &owningInstance->get())
{
return false;
}
}
}
return true;
}
void PrefabPublicHandler::EntityIdListToEntityList(const EntityIdList& inputEntityIds, EntityList& outEntities)
{
outEntities.reserve(inputEntityIds.size());
for (AZ::EntityId entityId : inputEntityIds)
{
if (entityId.IsValid())
{
outEntities.emplace_back(GetEntityById(entityId));
}
}
}
}
}
@@ -14,18 +14,22 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Math/Vector3.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
using EntityList = AZStd::vector<AZ::Entity*>;
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/PrefabUndoCache.h>
namespace AzToolsFramework
{
using EntityList = AZStd::vector<AZ::Entity*>;
namespace Prefab
{
class Instance;
class InstanceEntityMapperInterface;
class InstanceToTemplateInterface;
class PrefabSystemComponentInterface;
class PrefabPublicHandler final
: public PrefabPublicInterface
@@ -34,27 +38,48 @@ namespace AzToolsFramework
AZ_CLASS_ALLOCATOR(PrefabPublicHandler, AZ::SystemAllocator, 0);
AZ_RTTI(PrefabPublicHandler, "{35802943-6B60-430F-9DED-075E3A576A25}", PrefabPublicInterface);
PrefabPublicHandler();
~PrefabPublicHandler();
void RegisterPrefabPublicHandlerInterface();
void UnregisterPrefabPublicHandlerInterface();
// PrefabPublicInterface...
PrefabOperationResult CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath) override;
PrefabOperationResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, AZ::Vector3 position) override;
PrefabOperationResult SavePrefab(AZ::IO::Path filePath) override;
PrefabEntityResult CreateEntity(AZ::EntityId parentId, const AZ::Vector3& position) override;
void GenerateUndoNodesForEntityChangeAndUpdateCache(AZ::EntityId entityId, UndoSystem::URSequencePoint* parentUndoBatch) override;
bool IsInstanceContainerEntity(AZ::EntityId entityId) override;
AZ::EntityId GetInstanceContainerEntityId(AZ::EntityId entityId) override;
bool IsInstanceContainerEntity(AZ::EntityId entityId) const override;
bool IsLevelInstanceContainerEntity(AZ::EntityId entityId) const override;
AZ::EntityId GetInstanceContainerEntityId(AZ::EntityId entityId) const override;
AZ::EntityId GetLevelInstanceContainerEntityId() const override;
AZ::IO::Path GetOwningInstancePrefabPath(AZ::EntityId entityId) const override;
PrefabRequestResult HasUnsavedChanges(AZ::IO::Path prefabFilePath) const override;
PrefabOperationResult DeleteEntitiesInInstance(const EntityIdList& entityIds) override;
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
private:
PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants);
bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, const Instance& commonRootEntityOwningInstance,
EntityList& outEntities, AZStd::vector<AZStd::unique_ptr<Instance>>& outInstances) const;
//! Gets the owning instance of a valid commonRootEntity and the root prefab instance for an invalid commonRootEntity.
InstanceOptionalReference GetCommonRootEntityOwningInstance(AZ::EntityId entityId);
InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const;
bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const;
static Instance* GetParentInstance(Instance* instance);
static Instance* GetAncestorOfInstanceThatIsChildOfRoot(const Instance* ancestor, Instance* descendant);
static void GenerateContainerEntityTransform(const EntityList& topLevelEntities, AZ::Vector3& translation, AZ::Quaternion& rotation);
static void EntityIdListToEntityList(const EntityIdList& inputEntityIds, EntityList& outEntities);
InstanceEntityMapperInterface* m_instanceEntityMapperInterface;
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
// Caches entity states for undo/redo purposes
PrefabUndoCache m_prefabUndoCache;
uint64_t m_newEntityCounter = 1;
};
}
}
@@ -13,14 +13,24 @@
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzToolsFramework
{
using EntityIdList = AZStd::vector<AZ::EntityId>;
namespace UndoSystem
{
class URSequencePoint;
}
namespace Prefab
{
typedef AZ::Outcome<void, AZStd::string> PrefabOperationResult;
typedef AZ::Outcome<bool, AZStd::string> PrefabRequestResult;
typedef AZ::Outcome<AZ::EntityId, AZStd::string> PrefabEntityResult;
/*!
* PrefabPublicInterface
@@ -49,22 +59,91 @@ namespace AzToolsFramework
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, AZ::Vector3 position) = 0;
/**
* Saves changes to prefab to disk.
* @param filePath The path to the prefab to save.
* @return Returns Success if the file was saved, or an error message otherwise.
*/
virtual PrefabOperationResult SavePrefab(AZ::IO::Path filePath) = 0;
/**
* Creates a new entity under the entity with id 'parentId' and propagates a change to the template
* of the owning instance of parentId.
*
* @param parentId The id of the parent entity to parent the newly added entity under.
* @param position The transform position of the entity being added.
* @return Returns the entityId of the newly created entity, or an error message if the operation failed.
*/
virtual PrefabEntityResult CreateEntity(AZ::EntityId parentId, const AZ::Vector3& position) = 0;
/**
* Store the changes between the current entity state and its last cached state into undo/redo commands.
* These changes are stored as patches to the owning prefab instance template, as appropriate.
* The function also triggers the redo() of the nodes it creates, triggering propagation on the next tick.
*
* @param entityId The entity to patch.
* @param parentUndoBatch The undo batch the undo nodes should be parented to.
*/
virtual void GenerateUndoNodesForEntityChangeAndUpdateCache(
AZ::EntityId entityId, UndoSystem::URSequencePoint* parentUndoBatch) = 0;
/**
* Detects if an entity is the container entity for its owning prefab instance.
* @param entityId The entity to query.
* @return True if the entity is the container entity for its owning prefab instance, false otherwise.
*/
virtual bool IsInstanceContainerEntity(AZ::EntityId entityId) = 0;
virtual bool IsInstanceContainerEntity(AZ::EntityId entityId) const = 0;
/**
* Detects if an entity is the container entity for the level prefab instance.
* @param entityId The entity to query.
* @return True if the entity is the container entity for the level prefab instance, false otherwise.
*/
virtual bool IsLevelInstanceContainerEntity(AZ::EntityId entityId) const = 0;
/**
* Gets the entity id for the instance container of the owning instance.
* @param entityId The id of the entity to query.
* @return The entity id of the instance container owning the queried entity.
*/
virtual AZ::EntityId GetInstanceContainerEntityId(AZ::EntityId entityId) = 0;
};
virtual AZ::EntityId GetInstanceContainerEntityId(AZ::EntityId entityId) const = 0;
/**
* Gets the entity id for the instance container of the level instance.
* @return The entity id of the instance container for the currently loaded level.
*/
virtual AZ::EntityId GetLevelInstanceContainerEntityId() const = 0;
/**
* Get the file path to the prefab file for the prefab instance owning the entity provided.
* @param entityId The id for the entity being queried.
* @return Returns the path to the prefab, or an empty path if the entity is owned by the level.
*/
virtual AZ::IO::Path GetOwningInstancePrefabPath(AZ::EntityId entityId) const = 0;
/**
* Gets whether the prefab has unsaved changes.
* @param filePath The path to the prefab to query.
* @return Returns true if the prefab has unsaved changes, false otherwise. If path is invalid, returns an error message.
*/
virtual PrefabRequestResult HasUnsavedChanges(AZ::IO::Path prefabFilePath) const = 0;
/**
* Deletes all entities from the owning instance. Bails if the entities don't all belong to the same instance.
* @param entities The entities to delete.
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult DeleteEntitiesInInstance(const EntityIdList& entityIds) = 0;
/**
* Deletes all entities and their descendants from the owning instance. Bails if the entities don't all belong to the same
* instance.
* @param entities The entities to delete. Their descendants will be discovered by this function.
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) = 0;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -13,6 +13,7 @@
#include <AzToolsFramework/Prefab/PrefabSystemComponent.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
@@ -21,6 +22,7 @@
#include <AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
namespace AzToolsFramework
{
@@ -36,10 +38,14 @@ namespace AzToolsFramework
m_prefabLoader.RegisterPrefabLoaderInterface();
m_instanceUpdateExecutor.RegisterInstanceUpdateExecutorInterface();
m_instanceToTemplatePropagator.RegisterInstanceToTemplateInterface();
m_prefabPublicHandler.RegisterPrefabPublicHandlerInterface();
AZ::SystemTickBus::Handler::BusConnect();
}
void PrefabSystemComponent::Deactivate()
{
AZ::SystemTickBus::Handler::BusDisconnect();
m_prefabPublicHandler.UnregisterPrefabPublicHandlerInterface();
m_instanceToTemplatePropagator.UnregisterInstanceToTemplateInterface();
m_instanceUpdateExecutor.UnregisterInstanceUpdateExecutorInterface();
m_prefabLoader.UnregisterPrefabLoaderInterface();
@@ -80,18 +86,26 @@ namespace AzToolsFramework
{
}
AZStd::unique_ptr<Instance> PrefabSystemComponent::CreatePrefab(const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume, const AZStd::string& filePath)
void PrefabSystemComponent::OnSystemTick()
{
if (GetTemplateIdFromFilePath(filePath) != InvalidTemplateId)
m_instanceUpdateExecutor.UpdateTemplateInstancesInQueue();
}
AZStd::unique_ptr<Instance> PrefabSystemComponent::CreatePrefab(const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume,
AZ::IO::PathView filePath, AZStd::unique_ptr<AZ::Entity> containerEntity)
{
AZ::IO::Path relativeFilePath = m_prefabLoader.GetRelativePathToProject(filePath);
if (GetTemplateIdFromFilePath(relativeFilePath) != InvalidTemplateId)
{
AZ_Error("Prefab", false,
"Filepath %s has already been registered with the Prefab System Component",
filePath.c_str());
relativeFilePath.c_str());
return nullptr;
}
AZStd::unique_ptr<Instance> newInstance{ aznew Instance() };
AZStd::unique_ptr<Instance> newInstance = AZStd::make_unique<Instance>(AZStd::move(containerEntity));
for (AZ::Entity* entity : entities)
{
@@ -107,14 +121,14 @@ namespace AzToolsFramework
newInstance->AddInstance(AZStd::move(instance));
}
newInstance->SetTemplateSourcePath(filePath);
newInstance->SetTemplateSourcePath(relativeFilePath);
TemplateId newTemplateId = CreateTemplateFromInstance(*newInstance);
if (newTemplateId == InvalidTemplateId)
{
AZ_Error("Prefab", false,
"Failed to create a Template associated with file path %s during CreatePrefab.",
filePath.c_str());
relativeFilePath.c_str());
newInstance = nullptr;
}
@@ -154,10 +168,6 @@ namespace AzToolsFramework
void PrefabSystemComponent::UpdatePrefabInstances(const TemplateId& templateId)
{
m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId);
const bool updateResult = m_instanceUpdateExecutor.UpdateTemplateInstancesInQueue();
AZ_Assert(updateResult,
"Prefab - Error occurred while updating Instances of Template with id '%llu'.",
templateId);
}
void PrefabSystemComponent::UpdateLinkedInstances(AZStd::queue<LinkIds>& linkIdsQueue)
@@ -255,24 +265,26 @@ namespace AzToolsFramework
return nullptr;
}
Instance* newInstance = aznew Instance();
if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*newInstance, instantiatingTemplate->get().GetPrefabDom(), false))
auto newInstance = AZStd::make_unique<Instance>();
Instance::EntityList newEntities;
if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*newInstance, newEntities, instantiatingTemplate->get().GetPrefabDom()))
{
AZ_Error("Prefab", false,
"Failed to Load Prefab Template associated with path %s. Instantiation Failed",
instantiatingTemplate->get().GetFilePath().c_str());
delete newInstance;
return nullptr;
}
return AZStd::unique_ptr<Instance>(newInstance);
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, newEntities);
return newInstance;
}
TemplateId PrefabSystemComponent::CreateTemplateFromInstance(Instance& instance)
{
// We will register the template to match the path the instance has
const AZStd::string& templateSourcePath = instance.GetTemplateSourcePath();
const AZ::IO::Path& templateSourcePath = instance.GetTemplateSourcePath();
if (templateSourcePath.empty())
{
AZ_Assert(false,
@@ -290,7 +302,7 @@ namespace AzToolsFramework
}
// Generate a new template and store the dom data
const AZStd::string& instanceSourcePath = instance.GetTemplateSourcePath();
const AZ::IO::Path& instanceSourcePath = instance.GetTemplateSourcePath();
TemplateId newTemplateId = AddTemplate(instanceSourcePath, AZStd::move(serializedInstance));
if (newTemplateId == InvalidTemplateId)
{
@@ -354,7 +366,7 @@ namespace AzToolsFramework
}
}
TemplateId PrefabSystemComponent::AddTemplate(const AZStd::string& filePath, PrefabDom prefabDom)
TemplateId PrefabSystemComponent::AddTemplate(const AZ::IO::Path& filePath, PrefabDom prefabDom)
{
TemplateId newTemplateId = CreateUniqueTemplateId();
Template& newTemplate = m_templateIdMap.emplace(
@@ -378,6 +390,7 @@ namespace AzToolsFramework
}
m_templateFilePathToIdMap.emplace(AZStd::make_pair(filePath, newTemplateId));
newTemplate.MarkAsDirty(true);
return newTemplateId;
}
@@ -451,6 +464,23 @@ namespace AzToolsFramework
return;
}
void PrefabSystemComponent::RemoveAllTemplates()
{
AZStd::vector<TemplateId> templateIds;
templateIds.reserve(m_templateIdMap.size());
// Make a copy of the keys, we don't want to iterate over the map while we're removing items from it
for (const auto& [id, templateObject] : m_templateIdMap)
{
templateIds.emplace_back(id);
}
for (auto id : templateIds)
{
RemoveTemplate(id);
}
}
LinkId PrefabSystemComponent::AddLink(
const TemplateId& sourceTemplateId,
const TemplateId& targetTemplateId,
@@ -472,8 +502,8 @@ namespace AzToolsFramework
Template& targetTemplate = targetTemplateReference->get();
AZStd::string_view instanceName(instanceIterator->name.GetString(), instanceIterator->name.GetStringLength());
const AZStd::string& targetTemplateFilePath = targetTemplate.GetFilePath();
const AZStd::string& sourceTemplateFilePath = sourceTemplate.GetFilePath();
const AZStd::string& targetTemplateFilePath = targetTemplate.GetFilePath().Native();
const AZStd::string& sourceTemplateFilePath = sourceTemplate.GetFilePath().Native();
LinkId newLinkId = CreateUniqueLinkId();
Link newLink(newLinkId);
@@ -518,6 +548,7 @@ namespace AzToolsFramework
const TemplateId& linkTargetId,
const TemplateId& linkSourceId,
const InstanceAlias& instanceAlias,
const PrefabDomReference linkPatch,
const LinkId& linkId)
{
if (linkTargetId == InvalidTemplateId)
@@ -551,12 +582,6 @@ namespace AzToolsFramework
newLinkId = CreateUniqueLinkId();
}
//setup initial link values
Link newLink(newLinkId);
newLink.SetTargetTemplateId(linkTargetId);
newLink.SetSourceTemplateId(linkSourceId);
newLink.SetInstanceName(instanceAlias.c_str());
//get owner template and add the link
Template& targetTemplate = targetTemplateRef->get();
@@ -590,12 +615,22 @@ namespace AzToolsFramework
instancesValue->get().AddMember(rapidjson::StringRef(instanceAlias.c_str()), PrefabDomValue(), targetTemplateDom.GetAllocator());
//setup the link dom
Template& sourceTemplate = sourceTemplateRef->get();
//setup initial link values and link dom
Link newLink(newLinkId);
newLink.SetTargetTemplateId(linkTargetId);
newLink.SetSourceTemplateId(linkSourceId);
newLink.SetInstanceName(instanceAlias.c_str());
newLink.GetLinkDom().SetObject();
newLink.GetLinkDom().AddMember(rapidjson::StringRef(PrefabDomUtils::SourceName),
rapidjson::StringRef(sourceTemplate.GetFilePath().c_str()), newLink.GetLinkDom().GetAllocator());
if (linkPatch && linkPatch->get().IsArray() && !(linkPatch->get().Empty()))
{
m_instanceToTemplatePropagator.AddPatchesToLink(linkPatch.value(), newLink);
}
//update the target template dom to have the proper values for the source template dom
if (!newLink.UpdateTarget())
{
@@ -643,7 +678,7 @@ namespace AzToolsFramework
return;
}
TemplateId PrefabSystemComponent::GetTemplateIdFromFilePath(AZStd::string_view filePath) const
TemplateId PrefabSystemComponent::GetTemplateIdFromFilePath(AZ::IO::PathView filePath) const
{
auto found = m_templateFilePathToIdMap.find(filePath);
if (found != m_templateFilePathToIdMap.end())
@@ -656,6 +691,28 @@ namespace AzToolsFramework
}
}
bool PrefabSystemComponent::IsTemplateDirty(const TemplateId& templateId)
{
auto templateRef = FindTemplate(templateId);
if (templateRef.has_value())
{
return templateRef->get().IsDirty();
}
return false;
}
void PrefabSystemComponent::SetTemplateDirtyFlag(const TemplateId& templateId, bool dirty)
{
auto templateRef = FindTemplate(templateId);
if (templateRef.has_value())
{
templateRef->get().MarkAsDirty(dirty);
}
}
bool PrefabSystemComponent::ConnectTemplates(
Link& link,
TemplateId sourceTemplateId,
@@ -677,7 +734,7 @@ namespace AzToolsFramework
Template& targetTemplate = targetTemplateReference->get();
AZStd::string_view instanceName(instanceIterator->name.GetString(), instanceIterator->name.GetStringLength());
const AZStd::string& targetTemplateFilePath = targetTemplate.GetFilePath();
const AZStd::string& targetTemplateFilePath = targetTemplate.GetFilePath().Native();
link.SetSourceTemplateId(sourceTemplateId);
link.SetTargetTemplateId(targetTemplateId);
@@ -13,6 +13,7 @@
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/unordered_set.h>
@@ -30,10 +31,13 @@
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
namespace AzToolsFramework
namespace AZ
{
class Entity;
} // namespace AZ
namespace AzToolsFramework
{
using EntityList = AZStd::vector<AZ::Entity*>;
namespace Prefab
@@ -49,6 +53,7 @@ namespace AzToolsFramework
class PrefabSystemComponent
: public AZ::Component
, private PrefabSystemComponentInterface
, private AZ::SystemTickBus::Handler
{
public:
@@ -72,6 +77,9 @@ namespace AzToolsFramework
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
// SystemTickBus...
void OnSystemTick() override;
//////////////////////////////////////////////////////////////////////////
// PrefabSystemComponentInterface interface implementation
/**
@@ -94,7 +102,7 @@ namespace AzToolsFramework
* @param prefabDom A Prefab DOM presenting this Template.
* @return A unique id for the new Template.
*/
TemplateId AddTemplate(const AZStd::string& filePath, PrefabDom prefabDom) override;
TemplateId AddTemplate(const AZ::IO::Path& filePath, PrefabDom prefabDom) override;
/**
* Remove the Template associated with the given id from Prefab System Component.
@@ -102,6 +110,11 @@ namespace AzToolsFramework
*/
void RemoveTemplate(const TemplateId& templateId) override;
/**
* Remove all Templates from the Prefab System Component.
*/
void RemoveAllTemplates() override;
/**
* Generates a new Prefab Instance based on the Template referenced by templateId
* @param templateId the id of the template being instantiated
@@ -136,6 +149,7 @@ namespace AzToolsFramework
const TemplateId& linkTargetId,
const TemplateId& linkSourceId,
const InstanceAlias& instanceAlias,
const PrefabDomReference linkPatch,
const LinkId& linkId = InvalidLinkId) override;
/**
@@ -149,7 +163,22 @@ namespace AzToolsFramework
* @param filePath A path to a Prefab Template file.
* @return A unique id of Template on filePath. Return InvalidTemplateId if Template on filePath doesn't exist.
*/
TemplateId GetTemplateIdFromFilePath(AZStd::string_view filePath) const override;
TemplateId GetTemplateIdFromFilePath(AZ::IO::PathView filePath) const override;
/**
* Gets the value of the dirty flag of the template with the id provided.
* @param templateId The id of the template to query.
* @return The value of the dirty flag on the template.
*/
bool IsTemplateDirty(const TemplateId& templateId) override;
/**
* Sets the dirty flag of the template to the value provided.
* @param templateId The id of the template to flag.
* @param dirty The new value of the dirty flag.
*/
void SetTemplateDirtyFlag(const TemplateId& templateId, bool dirty) override;
//////////////////////////////////////////////////////////////////////////
/**
@@ -161,8 +190,8 @@ namespace AzToolsFramework
* @param filePath the path to associate the template of the new instance to
* @return A pointer to the newly created instance. nullptr on failure
*/
AZStd::unique_ptr<Instance> CreatePrefab(const AZStd::vector<AZ::Entity*> & entities,
AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume, const AZStd::string& filePath) override;
AZStd::unique_ptr<Instance> CreatePrefab(const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume,
AZ::IO::PathView filePath, AZStd::unique_ptr<AZ::Entity> containerEntity = nullptr) override;
PrefabDom& FindTemplateDom(TemplateId templateId) override;
@@ -302,7 +331,7 @@ namespace AzToolsFramework
AZStd::unordered_map<TemplateId, Template> m_templateIdMap;
// A container for mapping Templates' file paths to their Template ids.
AZStd::unordered_map<AZStd::string, TemplateId> m_templateFilePathToIdMap;
AZStd::unordered_map<AZ::IO::Path, TemplateId> m_templateFilePathToIdMap;
// A container of Prefab Links mapped by their Link ids.
AZStd::unordered_map<LinkId, Link> m_linkIdMap;
@@ -35,19 +35,24 @@ namespace AzToolsFramework
virtual TemplateReference FindTemplate(const TemplateId& id) = 0;
virtual LinkReference FindLink(const LinkId& id) = 0;
virtual TemplateId AddTemplate(const AZStd::string& filePath, PrefabDom prefabDom) = 0;
virtual TemplateId AddTemplate(const AZ::IO::Path& filePath, PrefabDom prefabDom) = 0;
virtual void RemoveTemplate(const TemplateId& templateId) = 0;
virtual void RemoveAllTemplates() = 0;
virtual LinkId AddLink(const TemplateId& sourceTemplateId, const TemplateId& targetTemplateId,
PrefabDomValue::MemberIterator& instanceIterator, InstanceOptionalReference instance) = 0;
//creates a new Link
virtual LinkId CreateLink(const TemplateId& linkTargetId, const TemplateId& linkSourceId,
const InstanceAlias& instanceAlias, const LinkId& linkId = InvalidLinkId) = 0;
const InstanceAlias& instanceAlias, const PrefabDomReference linkPatch,
const LinkId& linkId = InvalidLinkId) = 0;
virtual void RemoveLink(const LinkId& linkId) = 0;
virtual TemplateId GetTemplateIdFromFilePath(AZStd::string_view filePath) const = 0;
virtual TemplateId GetTemplateIdFromFilePath(AZ::IO::PathView filePath) const = 0;
virtual bool IsTemplateDirty(const TemplateId& templateId) = 0;
virtual void SetTemplateDirtyFlag(const TemplateId& templateId, bool dirty) = 0;
virtual PrefabDom& FindTemplateDom(TemplateId templateId) = 0;
virtual void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) = 0;
@@ -55,7 +60,8 @@ namespace AzToolsFramework
virtual AZStd::unique_ptr<Instance> InstantiatePrefab(const TemplateId& templateId) = 0;
virtual AZStd::unique_ptr<Instance> CreatePrefab(const AZStd::vector<AZ::Entity*>& entities,
AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume, const AZStd::string& filePath) = 0;
AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume, AZ::IO::PathView filePath,
AZStd::unique_ptr<AZ::Entity> containerEntity = nullptr) = 0;
};
@@ -12,8 +12,8 @@
#include <AzCore/Interface/Interface.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <Prefab/PrefabUndo.h>
#include <Prefab/PrefabDomUtils.h>
namespace AzToolsFramework
{
@@ -28,7 +28,6 @@ namespace AzToolsFramework
AZ_Assert(m_instanceToTemplateInterface, "Failed to grab instance to template interface");
}
//PrefabInstanceUndo
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName)
: PrefabUndoBase(undoOperationName)
@@ -85,12 +84,22 @@ namespace AzToolsFramework
void PrefabUndoEntityUpdate::Undo()
{
m_instanceToTemplateInterface->PatchEntityInTemplate(m_undoPatch, m_entityAlias, m_templateId);
bool isPatchApplicationSuccessful =
m_instanceToTemplateInterface->PatchEntityInTemplate(m_undoPatch, m_entityAlias, m_templateId);
AZ_Error(
"Prefab", isPatchApplicationSuccessful,
"Applying the undo patch on the entity with alias '%s' in template with id '%llu' was unsuccessful", m_entityAlias.c_str(),
m_templateId);
}
void PrefabUndoEntityUpdate::Redo()
{
m_instanceToTemplateInterface->PatchEntityInTemplate(m_redoPatch, m_entityAlias, m_templateId);
bool isPatchApplicationSuccessful =
m_instanceToTemplateInterface->PatchEntityInTemplate(m_redoPatch, m_entityAlias, m_templateId);
AZ_Error(
"Prefab", isPatchApplicationSuccessful,
"Applying the redo patch on the entity with alias '%s' in template with id '%llu' was unsuccessful", m_entityAlias.c_str(),
m_templateId);
}
//PrefabInstanceLinkUndo
@@ -100,7 +109,7 @@ namespace AzToolsFramework
, m_sourceId(InvalidTemplateId)
, m_instanceAlias("")
, m_linkId(InvalidLinkId)
, m_link(Link())
, m_linkDom(PrefabDom())
, m_linkStatus(LinkStatus::LINKSTATUS)
{
m_prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
@@ -111,14 +120,18 @@ namespace AzToolsFramework
const TemplateId& targetId,
const TemplateId& sourceId,
const InstanceAlias& instanceAlias,
const LinkId& linkId,
const Link& link)
const PrefabDomReference linkDom,
const LinkId linkId)
{
m_targetId = targetId;
m_sourceId = sourceId;
m_instanceAlias = instanceAlias;
m_linkId = linkId;
m_link = link;
if (linkDom.has_value())
{
m_linkDom = AZStd::move(linkDom->get());
}
//if linkId is invalid, set as ADD
if (m_linkId == InvalidLinkId)
@@ -169,21 +182,133 @@ namespace AzToolsFramework
m_prefabSystemComponentInterface->PropagateTemplateChanges(m_targetId);
}
LinkId PrefabUndoInstanceLink::GetLinkId()
{
return m_linkId;
}
void PrefabUndoInstanceLink::AddLink()
{
m_linkId = m_prefabSystemComponentInterface->CreateLink(m_targetId, m_sourceId, m_instanceAlias, m_linkId);
//if data already exists, repopulate
if (m_linkStatus == LinkStatus::REMOVE)
{
LinkReference link = m_prefabSystemComponentInterface->FindLink(m_linkId);
link = m_link;
}
m_linkId = m_prefabSystemComponentInterface->CreateLink(m_targetId, m_sourceId, m_instanceAlias, m_linkDom, m_linkId);
}
void PrefabUndoInstanceLink::RemoveLink()
{
m_prefabSystemComponentInterface->RemoveLink(m_linkId);
}
//PrefabUndoLinkUpdate
PrefabUndoLinkUpdate::PrefabUndoLinkUpdate(const AZStd::string& undoOperationName)
: PrefabUndoBase(undoOperationName)
, m_linkId(InvalidLinkId)
, m_linkDomNext(PrefabDom())
, m_linkDomPrevious(PrefabDom())
{
m_prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
AZ_Assert(m_instanceToTemplateInterface, "Failed to grab interface");
}
void PrefabUndoLinkUpdate::Capture(
const PrefabDom& patch,
const LinkId linkId)
{
m_linkId = linkId;
//acquire link and existing values
LinkReference link = m_prefabSystemComponentInterface->FindLink(m_linkId);
if (link == AZStd::nullopt)
{
AZ_Error("Prefab", false, "PrefabUndoLinkUpdate: Link not found");
return;
}
if (link.has_value())
{
m_linkDomPrevious = AZStd::move(link->get().GetLinkDom());
}
//get source templateDom
TemplateReference sourceTemplate = m_prefabSystemComponentInterface->FindTemplate(link->get().GetSourceTemplateId());
if (sourceTemplate == AZStd::nullopt)
{
AZ_Error("Prefab", false, "PrefabUndoLinkUpdate: Source template not found");
return;
}
PrefabDomReference sourceDom = sourceTemplate->get().GetPrefabDom();
//use instance pointer to reach position
PrefabDomValueReference instanceDomRef = link->get().GetLinkedInstanceDom();
//copy the target instance the link is pointing to
PrefabDom instanceDom;
instanceDom.CopyFrom(instanceDomRef->get(), instanceDom.GetAllocator());
//apply the patch to the template within the target
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::ApplyPatch(instanceDom,
instanceDom.GetAllocator(), patch, AZ::JsonMergeApproach::JsonPatch);
//remove the link id placed into the instance
auto linkIdIter = instanceDom.FindMember(PrefabDomUtils::LinkIdName);
if (linkIdIter != instanceDom.MemberEnd())
{
instanceDom.RemoveMember(PrefabDomUtils::LinkIdName);
}
//we use this to diff our copy against the vanilla template (source template)
PrefabDom patchLink;
m_instanceToTemplateInterface->GeneratePatch(patchLink, sourceDom->get(), instanceDom);
// Create a copy of patchLink by providing the allocator of m_linkDomNext so that the patch doesn't become invalid when
// the patch goes out of scope in this function.
PrefabDom patchLinkCopy;
patchLinkCopy.CopyFrom(patchLink, m_linkDomNext.GetAllocator());
m_linkDomNext.CopyFrom(m_linkDomPrevious, m_linkDomNext.GetAllocator());
auto patchesIter = m_linkDomNext.FindMember(PrefabDomUtils::PatchesName);
if (patchesIter == m_linkDomNext.MemberEnd())
{
m_linkDomNext.AddMember(
rapidjson::GenericStringRef(PrefabDomUtils::PatchesName), patchLinkCopy, m_linkDomNext.GetAllocator());
}
else
{
patchesIter->value = AZStd::move(patchLinkCopy.GetArray());
}
}
void PrefabUndoLinkUpdate::Undo()
{
UpdateLink(m_linkDomPrevious);
}
void PrefabUndoLinkUpdate::Redo()
{
UpdateLink(m_linkDomNext);
}
void PrefabUndoLinkUpdate::UpdateLink(PrefabDom& linkDom)
{
LinkReference link = m_prefabSystemComponentInterface->FindLink(m_linkId);
if (link == AZStd::nullopt)
{
AZ_Error("Prefab", false, "PrefabUndoLinkUpdate: Link not found");
return;
}
PrefabDom moveLink;
moveLink.CopyFrom(linkDom, linkDom.GetAllocator());
link->get().GetLinkDom() = AZStd::move(moveLink);
//propagate the link changes
link->get().UpdateTarget();
m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId());
//mark as dirty
m_prefabSystemComponentInterface->SetTemplateDirtyFlag(link->get().GetTargetTemplateId(), true);
}
}
}
@@ -64,6 +64,9 @@ namespace AzToolsFramework
: public PrefabUndoBase
{
public:
AZ_RTTI(PrefabUndoEntityUpdate, "{6D60C5A6-9535-45B3-8897-E5F6382FDC93}", PrefabUndoBase);
AZ_CLASS_ALLOCATOR(PrefabUndoEntityUpdate, AZ::SystemAllocator, 0);
explicit PrefabUndoEntityUpdate(const AZStd::string& undoOperationName);
void Capture(
@@ -88,7 +91,6 @@ namespace AzToolsFramework
{
ADD,
REMOVE,
UPDATE,
LINKSTATUS
};
@@ -99,12 +101,14 @@ namespace AzToolsFramework
const TemplateId& targetId,
const TemplateId& sourceId,
const InstanceAlias& instanceAlias,
const LinkId& linkId = InvalidLinkId,
const Link& link = Link());
const PrefabDomReference linkDom = PrefabDomReference(),
const LinkId linkId = InvalidLinkId);
void Undo() override;
void Redo() override;
LinkId GetLinkId();
private:
//used for special cases of add/delete
void AddLink();
@@ -116,10 +120,35 @@ namespace AzToolsFramework
InstanceAlias m_instanceAlias;
LinkId m_linkId;
Link m_link; //data for delete/update
PrefabDom m_linkDom; //data for delete/update
LinkStatus m_linkStatus;
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
};
class PrefabUndoLinkUpdate
: public PrefabUndoBase
{
public:
explicit PrefabUndoLinkUpdate(const AZStd::string& undoOperationName);
//capture for add/remove
void Capture(
const PrefabDom& patch,
const LinkId linkId = InvalidLinkId);
void Undo() override;
void Redo() override;
private:
void UpdateLink(PrefabDom& linkDom);
LinkId m_linkId;
PrefabDom m_linkDomNext; //data for delete/update
PrefabDom m_linkDomPrevious; //stores the data for undo
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
};
}
}
@@ -0,0 +1,183 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/Prefab/PrefabUndoCache.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Interface/Interface.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h>
namespace AzToolsFramework
{
namespace Prefab
{
void PrefabUndoCache::Initialize()
{
m_instanceToTemplateInterface = AZ::Interface<InstanceToTemplateInterface>::Get();
AZ_Assert(m_instanceToTemplateInterface, "PrefabUndoCache - Could not retrieve instance of InstanceToTemplateInterface");
m_instanceEntityMapperInterface = AZ::Interface<InstanceEntityMapperInterface>::Get();
AZ_Assert(m_instanceEntityMapperInterface, "PrefabUndoCache - Could not retrieve instance of InstanceEntityMapperInterface");
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (prefabSystemEnabled)
{
// By default, ToolsApplication will register the regular PreemptiveCache as the handler of this interface.
// Since the SettingsRegistry isn't active when ToolsApplication is constructed, and Start and StartCommon
// aren't called during tests, we have to resort to unregistering the Preemptive cache here, and registering
// the PrefabCache in its place. Both caches check if they're registered before unregistering on destroy.
auto preemptiveCache = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
if (preemptiveCache)
{
AZ::Interface<UndoSystem::UndoCacheInterface>::Unregister(preemptiveCache);
}
AZ::Interface<UndoSystem::UndoCacheInterface>::Register(this);
}
}
void PrefabUndoCache::Destroy()
{
if (AZ::Interface<UndoSystem::UndoCacheInterface>::Get() == this)
{
AZ::Interface<UndoSystem::UndoCacheInterface>::Unregister(this);
}
}
void PrefabUndoCache::Validate(const AZ::EntityId& entityId)
{
(void)entityId;
#if defined(ENABLE_UNDOCACHE_CONSISTENCY_CHECKS)
if (entityId == AZ::SystemEntityId)
{
return;
}
PrefabDom oldData;
Retrieve(entityId, oldData);
UpdateCache(entityId);
PrefabDom newData;
Retrieve(entityId, newData);
if (newData != oldData)
{
// display a useful message
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
if (!entity)
{
AZ_Warning(
"Undo", false,
"Undo system wasn't informed about the deletion of entity %p - make sure you call DeleteEntity, instead of "
"directly deleting it.\n",
entityId);
return;
}
AZ_Warning(
"Undo", false,
"Undo system has inconsistent data for entity %p (%s)\n Ensure that SetDirty is "
"being called (or WorldEditor::WorldEditorMessages::Bus, AddDirtyEntity...) for modified entities.",
entityId, entity->GetName().c_str());
}
// Clear out newly generated data and
// replace with original data to ensure debug mode has the same data as profile/release
// in the event of the consistency check failing.
m_entitySavedStates[entityId] = AZStd::move(oldData);
#endif // ENABLE_UNDOCACHE_CONSISTENCY_CHECKS
}
void PrefabUndoCache::UpdateCache(const AZ::EntityId& entityId)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
if (!entity)
{
AZ_Warning(
"Undo", false,
"PrefabUndoCache was told to update the cache for entity of id %llu, but that entity is not available in "
"FindEntity",
static_cast<AZ::u64>(entityId)
);
return;
}
InstanceOptionalReference instanceOptionalReference = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
if (!instanceOptionalReference.has_value())
{
AZ_Warning(
"Undo", false,
"PrefabUndoCache was told to update the cache for entity of id %p (%s), but that entity does not have an owning instance.",
entityId, entity->GetName().c_str());
return;
}
// Capture it
PrefabDom entityDom;
m_instanceToTemplateInterface->GenerateDomForEntity(entityDom, *entity);
m_entitySavedStates.emplace(AZStd::make_pair(entityId, AZStd::move(entityDom)));
AZLOG("Prefab Undo", "Correctly updated cache for entity of id %llu (%s)", static_cast<AZ::u64>(entityId), entity->GetName().c_str());
return;
}
void PrefabUndoCache::PurgeCache(const AZ::EntityId& entityId)
{
m_entitySavedStates.erase(entityId);
}
bool PrefabUndoCache::Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom)
{
auto it = m_entitySavedStates.find(entityId);
if (it == m_entitySavedStates.end())
{
return false;
}
outDom = AZStd::move(m_entitySavedStates[entityId]);
m_entitySavedStates.erase(entityId);
return true;
}
void PrefabUndoCache::Store(const AZ::EntityId& entityId, PrefabDom&& dom)
{
m_entitySavedStates.emplace(AZStd::make_pair(entityId, AZStd::move(dom)));
}
void PrefabUndoCache::Clear()
{
m_entitySavedStates.clear();
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -0,0 +1,63 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/EntityId.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/Undo/UndoCacheInterface.h>
namespace AzToolsFramework
{
namespace UndoSystem
{
class URSequencePoint;
}
namespace Prefab
{
class InstanceEntityMapperInterface;
class InstanceToTemplateInterface;
class PrefabUndoCache
: UndoSystem::UndoCacheInterface
{
public:
AZ_CLASS_ALLOCATOR(PrefabUndoCache, AZ::SystemAllocator, 0);
void Initialize();
void Destroy();
// UndoCacheInterface...
void UpdateCache(const AZ::EntityId& entityId) override;
void PurgeCache(const AZ::EntityId& entityId) override;
void Clear() override;
void Validate(const AZ::EntityId& entityId) override;
// Retrieve the last known state for an entity
bool Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom);
// Store dom as the cached state of entityId
void Store(const AZ::EntityId& entityId, PrefabDom&& dom);
private:
typedef AZStd::unordered_map<AZ::EntityId, PrefabDom> EntityDomMap;
EntityDomMap m_entitySavedStates;
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -40,18 +40,18 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
return;
}
prefabProcessorContext.ListPrefabs([this, &serializeContext, &prefabProcessorContext](AZStd::string_view prefabName, PrefabDom& prefab)
{
auto result = RemoveEditorInfo(prefab, serializeContext, prefabProcessorContext);
if (!result)
prefabProcessorContext.ListPrefabs(
[this, &serializeContext, &prefabProcessorContext](AZStd::string_view prefabName, PrefabDom& prefab)
{
AZ_Assert(false,
"Converting to runtime Prefab '%.*s' failed, Error: %s .",
AZ_STRING_ARG(prefabName),
result.GetError().c_str());
return;
}
});
auto result = RemoveEditorInfo(prefab, serializeContext, prefabProcessorContext);
if (!result)
{
AZ_Error(
"Prefab", false, "Converting to runtime Prefab '%.*s' failed, Error: %s .", AZ_STRING_ARG(prefabName),
result.GetError().c_str());
return;
}
});
}
void EditorInfoRemover::Reflect(AZ::ReflectContext* context)
@@ -74,6 +74,12 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
}
);
if (instance->HasContainerEntity())
{
auto containerEntityReference = instance->GetContainerEntity();
result.emplace_back(&containerEntityReference->get());
}
return result;
}
@@ -519,7 +525,8 @@ exportComponent, prefabProcessorContext);
// convert Prefab DOM into Prefab Instance.
AZStd::unique_ptr<Instance> instance(aznew Instance());
if (!Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*instance, prefab, false))
if (!Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*instance, prefab,
Prefab::PrefabDomUtils::LoadInstanceFlags::AssignRandomEntityId))
{
PrefabDomValueReference sourceReference = PrefabDomUtils::FindPrefabDomValue(prefab, PrefabDomUtils::SourceName);
@@ -613,19 +620,28 @@ exportComponent, prefabProcessorContext);
[&exportEntitiesMap](AZStd::unique_ptr<AZ::Entity>& entity)
{
auto entityId = entity->GetId();
entity.release();
entity.reset(exportEntitiesMap[entityId]);
return true;
}
);
if (instance->HasContainerEntity())
{
if (auto found = exportEntitiesMap.find(instance->GetContainerEntityId()); found != exportEntitiesMap.end())
{
instance->SetContainerEntity(*found->second);
}
}
// save the final result in the target Prefab DOM.
if (!PrefabDomUtils::StoreInstanceInPrefabDom(*instance, prefab))
PrefabDom filteredPrefab;
if (!PrefabDomUtils::StoreInstanceInPrefabDom(*instance, filteredPrefab))
{
return AZ::Failure(AZStd::string::format(
"Saving exported Prefab Instance within a Prefab Dom failed.")
);
}
prefab.Swap(filteredPrefab);
return AZ::Success();
}
@@ -16,6 +16,7 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.h>
#include <AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h>
@@ -23,9 +24,11 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
void PrefabCatchmentProcessor::Process(PrefabProcessorContext& context)
{
context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab)
AZ::DataStream::StreamType serializationFormat = m_serializationFormat == SerializationFormats::Binary ?
AZ::DataStream::StreamType::ST_BINARY : AZ::DataStream::StreamType::ST_XML;
context.ListPrefabs([&context, serializationFormat](AZStd::string_view prefabName, PrefabDom& prefab)
{
ProcessPrefab(context, prefabName, prefab);
ProcessPrefab(context, prefabName, prefab, serializationFormat);
});
}
@@ -33,31 +36,61 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<PrefabCatchmentProcessor, PrefabProcessor>()->Version(1);
serializeContext->Enum<SerializationFormats>()
->Value("Binary", SerializationFormats::Binary)
->Value("Text", SerializationFormats::Text);
serializeContext->Class<PrefabCatchmentProcessor, PrefabProcessor>()
->Version(2)
->Field("SerializationFormat", &PrefabCatchmentProcessor::m_serializationFormat);
}
}
void PrefabCatchmentProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab)
void PrefabCatchmentProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab,
AZ::DataStream::StreamType serializationFormat)
{
AZStd::string uniqueName = prefabName;
uniqueName += '.';
uniqueName += AzFramework::Spawnable::FileExtension;
uniqueName += AzFramework::Spawnable::DotFileExtension;
auto serializer = [](AZStd::vector<uint8_t>& output, const ProcessedObjectStore& object) -> bool
auto serializer = [serializationFormat](AZStd::vector<uint8_t>& output, const ProcessedObjectStore& object) -> bool
{
AZ::IO::ByteContainerStream stream(&output);
return AZ::Utils::SaveObjectToStream(stream, AZ::DataStream::StreamType::ST_BINARY,
AZStd::any_cast<void>(&object.GetObject()), object.GetObject().type());
auto& asset = object.GetAsset();
return AZ::Utils::SaveObjectToStream(stream, serializationFormat, &asset, asset.GetType());
};
auto spawnable = SpawnableUtils::CreateSpawnable(prefab);
SpawnableUtils::SortEntitiesByTransformHierarchy(spawnable);
AZStd::any spawnableAny(AZStd::move(spawnable));
auto&& [object, spawnable] = ProcessedObjectStore::Create<AzFramework::Spawnable>(
AZStd::move(uniqueName), context.GetSourceUuid(), AZStd::move(serializer));
AZ_Assert(spawnable, "Failed to create a new spawnable.");
context.GetProcessedObjects().emplace_back(AZStd::move(uniqueName), AZStd::move(spawnableAny),
AZStd::move(serializer), AZ::AzTypeInfo<AzFramework::Spawnable>::Uuid());
bool result = SpawnableUtils::CreateSpawnable(*spawnable, prefab);
if (result)
{
AzFramework::Spawnable::EntityList& entities = spawnable->GetEntities();
for (auto it = entities.begin(); it != entities.end(); )
{
(*it)->InvalidateDependencies();
AZ::Entity::DependencySortOutcome evaluation = (*it)->EvaluateDependenciesGetDetails();
if (evaluation.IsSuccess())
{
++it;
}
else
{
AZ_Error("Prefabs", false, "Entity '%s' %s cannot be activated for the following reason: %s",
(*it)->GetName().c_str(), (*it)->GetId().ToString().c_str(), evaluation.GetError().m_message.c_str());
it = entities.erase(it);
}
}
SpawnableUtils::SortEntitiesByTransformHierarchy(*spawnable);
context.GetProcessedObjects().push_back(AZStd::move(object));
context.RemovePrefab(prefabName);
context.RemovePrefab(prefabName);
}
else
{
AZ_Error("Prefabs", false, "Failed to convert prefab '%.*s' to a spawnable.", AZ_STRING_ARG(prefabName));
context.ErrorEncountered();
}
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -30,6 +30,13 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
AZ_RTTI(AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor,
"{F71E2FBA-22ED-44C7-B4C8-D2CF4B2C7B97}", PrefabProcessor);
//! The format the remaining spawnables are going to be stored in.
enum class SerializationFormats
{
Binary, //!< Binary is generally preferable for performance.
Text //!< Store in text format which is usually slower but helps with debugging.
};
~PrefabCatchmentProcessor() override = default;
void Process(PrefabProcessorContext& context) override;
@@ -37,6 +44,15 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
static void Reflect(AZ::ReflectContext* context);
protected:
static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab);
static void ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab,
AZ::DataStream::StreamType serializationFormat);
SerializationFormats m_serializationFormat{ SerializationFormats::Binary };
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
namespace AZ
{
AZ_TYPE_INFO_SPECIALIZE(AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor::SerializationFormats,
"{0FBE2482-B514-4256-8716-EA3ECDF8CD49}");
}
@@ -25,7 +25,30 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
auto registry = AZ::SettingsRegistry::Get();
AZ_Assert(registry, "PrefabConversionPipeline is created before the Settings Registry is available.");
return registry->GetObject(m_processors, registryKey);
bool result = registry->GetObject(m_processors, registryKey);
if (!result)
{
m_processors.clear();
}
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (serializeContext)
{
m_fingerprint = CalculateProcessorFingerprint(serializeContext);
}
else
{
AZ_Error("PrefabConversionPipeline", false, "Failed to get serialization context");
}
return result;
}
bool PrefabConversionPipeline::IsLoaded() const
{
return !m_processors.empty();
}
void PrefabConversionPipeline::ProcessPrefab(PrefabProcessorContext& context)
@@ -35,6 +58,26 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
processor->Process(context);
}
}
size_t PrefabConversionPipeline::CalculateProcessorFingerprint(AZ::SerializeContext* context)
{
size_t fingerprint = 0;
for (const auto& processor : m_processors)
{
const AZ::SerializeContext::ClassData* classData = context->FindClassData(processor->RTTI_GetType());
if (!classData)
{
AZ_Warning("PrefabConversionPipeline", false, "Class data for processor type %s not found. Cannot get version for fingerprinting", processor->RTTI_GetType().ToString<AZStd::string>().c_str());
continue;
}
AZStd::hash_combine(fingerprint, processor->RTTI_GetType());
AZStd::hash_combine(fingerprint, classData->m_version);
}
return fingerprint;
}
void PrefabConversionPipeline::Reflect(AZ::ReflectContext* context)
{
@@ -45,4 +88,9 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
serializeContext->RegisterGenericType<PrefabProcessorListEntry>();
}
}
size_t PrefabConversionPipeline::GetFingerprint() const
{
return m_fingerprint;
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -31,12 +31,18 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
using PrefabProcessorList = AZStd::vector<PrefabProcessorListEntry>;
bool LoadStackProfile(AZStd::string_view stackProfile);
bool IsLoaded() const;
void ProcessPrefab(PrefabProcessorContext& context);
static void Reflect(AZ::ReflectContext* context);
size_t GetFingerprint() const;
static void Reflect(AZ::ReflectContext* context);
private:
size_t CalculateProcessorFingerprint(AZ::SerializeContext* context);
PrefabProcessorList m_processors;
size_t m_fingerprint{};
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -14,6 +14,10 @@
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
PrefabProcessorContext::PrefabProcessorContext(const AZ::Uuid& sourceUuid)
: m_sourceUuid(sourceUuid)
{}
bool PrefabProcessorContext::AddPrefab(AZStd::string prefabName, PrefabDom prefab)
{
auto result = m_prefabs.emplace(AZStd::move(prefabName), AZStd::move(prefab));
@@ -76,9 +80,28 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
return m_products;
}
void PrefabProcessorContext::SetPlatformTags(AZ::PlatformTagSet tags)
{
m_platformTags = AZStd::move(tags);
}
const AZ::PlatformTagSet& PrefabProcessorContext::GetPlatformTags() const
{
return m_platformTags;
}
const AZ::Uuid& PrefabProcessorContext::GetSourceUuid() const
{
return m_sourceUuid;
}
bool PrefabProcessorContext::HasCompletedSuccessfully() const
{
return m_completedSuccessfully;
}
void PrefabProcessorContext::ErrorEncountered()
{
m_completedSuccessfully = false;
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -33,6 +33,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
AZ_CLASS_ALLOCATOR(PrefabProcessorContext, AZ::SystemAllocator, 0);
AZ_RTTI(PrefabProcessorContext, "{C7D77E3A-C544-486B-B774-7C82C38FE22F}");
explicit PrefabProcessorContext(const AZ::Uuid& sourceUuid);
virtual ~PrefabProcessorContext() = default;
virtual bool AddPrefab(AZStd::string prefabName, PrefabDom prefab);
@@ -44,7 +45,12 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
virtual ProcessedObjectStoreContainer& GetProcessedObjects();
virtual const ProcessedObjectStoreContainer& GetProcessedObjects() const;
virtual void SetPlatformTags(AZ::PlatformTagSet tags);
virtual const AZ::PlatformTagSet& GetPlatformTags() const;
virtual const AZ::Uuid& GetSourceUuid() const;
virtual bool HasCompletedSuccessfully() const;
virtual void ErrorEncountered();
protected:
using NamedPrefabContainer = AZStd::unordered_map<AZStd::string, PrefabDom>;
@@ -53,6 +59,8 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
ProcessedObjectStoreContainer m_products;
AZStd::vector<AZStd::string> m_delayedDelete;
AZ::PlatformTagSet m_platformTags;
AZ::Uuid m_sourceUuid;
bool m_isIterating{ false };
bool m_completedSuccessfully{ true };
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -11,25 +11,21 @@
*/
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/Math/Uuid.h>
#include <AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
ProcessedObjectStore::ProcessedObjectStore(AZStd::string uniqueId, AZStd::any object, SerializerFunction objectSerializer,
AZ::Data::AssetType assetType)
ProcessedObjectStore::ProcessedObjectStore(AZStd::string uniqueId, AZStd::unique_ptr<AZ::Data::AssetData> asset, SerializerFunction assetSerializer)
: m_uniqueId(AZStd::move(uniqueId))
, m_object(AZStd::move(object))
, m_objectSerializer(AZStd::move(objectSerializer))
, m_assetType(AZStd::move(assetType))
{
}
, m_assetSerializer(AZStd::move(assetSerializer))
, m_asset(AZStd::move(asset))
{}
bool ProcessedObjectStore::Serialize(AZStd::vector<uint8_t>& output) const
{
if (m_objectSerializer)
if (m_assetSerializer)
{
return m_objectSerializer(output, *this);
return m_assetSerializer(output, *this);
}
else
{
@@ -37,25 +33,38 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
}
}
const AZStd::any& ProcessedObjectStore::GetObject() const
bool ProcessedObjectStore::HasAsset() const
{
return m_object;
}
AZStd::any ProcessedObjectStore::ReleaseObject()
{
return AZStd::move(m_object);
}
uint32_t ProcessedObjectStore::BuildSubId() const
{
AZ::Uuid subIdHash = AZ::Uuid::CreateData(m_uniqueId.data(), m_uniqueId.size());
return azlossy_caster(subIdHash.GetHash());
return m_asset != nullptr;
}
const AZ::Data::AssetType& ProcessedObjectStore::GetAssetType() const
{
return m_assetType;
AZ_Assert(m_asset, "Called GetAssetType on ProcessedObjectStore when there was no valid asset.");
return m_asset->GetType();
}
AZ::Data::AssetData& ProcessedObjectStore::GetAsset()
{
AZ_Assert(m_asset, "Called GetAsset on ProcessedObjectStore when there was no valid asset.");
return *m_asset;
}
const AZ::Data::AssetData& ProcessedObjectStore::GetAsset() const
{
AZ_Assert(m_asset, "Called GetAsset on ProcessedObjectStore when there was no valid asset.");
return *m_asset;
}
AZStd::unique_ptr<AZ::Data::AssetData> ProcessedObjectStore::ReleaseAsset()
{
return AZStd::move(m_asset);
}
uint32_t ProcessedObjectStore::BuildSubId(AZStd::string_view id)
{
AZ::Uuid subIdHash = AZ::Uuid::CreateData(id.data(), id.size());
return azlossy_caster(subIdHash.GetHash());
}
const AZStd::string& ProcessedObjectStore::GetId() const
@@ -13,10 +13,11 @@
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/any.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/utils.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/typetraits/typetraits.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
@@ -31,26 +32,42 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
//! Constructs a new instance.
//! @param uniqueId A name for the object that's unique within the scope of the Prefab. This name will be used to generate a sub id for the product
//! which requires that the name is stable between runs.
//! @param object The object that generated during processing of a Prefab.
//! @param objectSerializer The callback used to convert the provided object into a binary stream.
//! @param assetType The asset type of the asset.
//! @param storagePath The relative path where the asset will be stored if/when committed to disk.
ProcessedObjectStore(AZStd::string uniqueId, AZStd::any object, SerializerFunction objectSerializer, AZ::Data::AssetType assetType);
//! which requires that the name to be stable between runs.
//! @param sourceId The uuid for the source file.
//! @param assetSerializer The callback used to convert the provided asset into a binary version.
template<typename T>
static AZStd::pair<ProcessedObjectStore, T*> Create(AZStd::string uniqueId, const AZ::Uuid& sourceId,
SerializerFunction assetSerializer);
bool Serialize(AZStd::vector<uint8_t>& output) const;
uint32_t BuildSubId() const;
const AZStd::any& GetObject() const;
AZStd::any ReleaseObject();
static uint32_t BuildSubId(AZStd::string_view id);
bool HasAsset() const;
const AZ::Data::AssetType& GetAssetType() const;
const AZ::Data::AssetData& GetAsset() const;
AZ::Data::AssetData& GetAsset();
AZStd::unique_ptr<AZ::Data::AssetData> ReleaseAsset();
const AZStd::string& GetId() const;
private:
AZStd::any m_object;
SerializerFunction m_objectSerializer;
AZ::Data::AssetType m_assetType;
ProcessedObjectStore(AZStd::string uniqueId, AZStd::unique_ptr<AZ::Data::AssetData> asset, SerializerFunction assetSerializer);
SerializerFunction m_assetSerializer;
AZStd::unique_ptr<AZ::Data::AssetData> m_asset;
AZStd::string m_uniqueId;
};
template<typename T>
AZStd::pair<ProcessedObjectStore, T*> ProcessedObjectStore::Create(AZStd::string uniqueId, const AZ::Uuid& sourceId,
SerializerFunction assetSerializer)
{
static_assert(AZStd::is_base_of_v<AZ::Data::AssetData, T>,
"ProcessedObjectStore can only be created from a class that derives from AZ::Data::AssetData.");
AZ::Data::AssetId assetId(sourceId, BuildSubId(uniqueId));
auto instance = AZStd::make_unique<T>(assetId, AZ::Data::AssetData::AssetStatus::Ready);
ProcessedObjectStore resultLeft(AZStd::move(uniqueId), AZStd::move(instance), AZStd::move(assetSerializer));
T* resultRight = static_cast<T*>(&resultLeft.GetAsset());
return AZStd::make_pair<ProcessedObjectStore, T*>(AZStd::move(resultLeft), resultRight);
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -17,9 +17,10 @@
#include <AzCore/Component/TransformBus.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzFramework/Spawnable/Spawnable.h>
namespace AzToolsFramework::Prefab::SpawnableUtils
{
@@ -27,22 +28,35 @@ namespace AzToolsFramework::Prefab::SpawnableUtils
AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom)
{
AzFramework::Spawnable spawnable;
AZStd::unique_ptr<Instance> instance(aznew Instance());
if (!Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*instance, prefabDom, false))
[[maybe_unused]] bool result = CreateSpawnable(spawnable, prefabDom);
AZ_Assert(result,
"Failed to Load Prefab Instance from given Prefab DOM while Spawnable creation.");
return spawnable;
}
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom)
{
Instance instance;
if (Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(instance, prefabDom,
Prefab::PrefabDomUtils::LoadInstanceFlags::AssignRandomEntityId)) // Always assign random entity ids because the spawnable is
// going to be used to create clones of the entities.
{
AZ_Assert(false,
"Failed to Load Prefab Instance from given Prefab DOM while Spawnable creation.");
AzFramework::Spawnable::EntityList& entities = spawnable.GetEntities();
if (instance.HasContainerEntity())
{
entities.emplace_back(AZStd::move(instance.DetachContainerEntity()));
}
instance.DetachNestedEntities(
[&entities](AZStd::unique_ptr<AZ::Entity> entity)
{
entities.emplace_back(AZStd::move(entity));
});
return true;
}
else
{
AzFramework::Spawnable::EntityList& entities = spawnable.GetEntities();
instance->DetachNestedEntities([&entities](AZStd::unique_ptr<AZ::Entity> entity)
{
entities.emplace_back(AZStd::move(entity));
});
return false;
}
return spawnable;
}
void OrganizeEntitiesForSorting(
@@ -18,6 +18,7 @@
namespace AzToolsFramework::Prefab::SpawnableUtils
{
AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom);
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom);
void SortEntitiesByTransformHierarchy(AzFramework::Spawnable& spawnable);
} // namespace AzToolsFramework::Prefab::SpawnableUtils
@@ -20,7 +20,7 @@ namespace AzToolsFramework
{
namespace Prefab
{
Template::Template(const AZStd::string& filePath, PrefabDom prefabDom)
Template::Template(const AZ::IO::Path& filePath, PrefabDom prefabDom)
: m_filePath(filePath)
, m_prefabDom(AZStd::move(prefabDom))
{
@@ -83,6 +83,16 @@ namespace AzToolsFramework
m_isLoadedWithErrors = loadedWithErrors;
}
bool Template::IsDirty() const
{
return m_isDirty;
}
void Template::MarkAsDirty(bool dirty)
{
m_isDirty = dirty;
}
bool Template::AddLink(LinkId newLinkId)
{
if (newLinkId == InvalidLinkId)
@@ -236,7 +246,7 @@ namespace AzToolsFramework
return findInstancesResult->get();
}
const AZStd::string& Template::GetFilePath() const
const AZ::IO::Path& Template::GetFilePath() const
{
return m_filePath;
}
@@ -12,6 +12,7 @@
#pragma once
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/std/containers/unordered_set.h>
@@ -37,7 +38,7 @@ namespace AzToolsFramework
using Links = AZStd::unordered_set<LinkId>;
Template() = default;
Template(const AZStd::string& filePath, PrefabDom prefabDom);
Template(const AZ::IO::Path& filePath, PrefabDom prefabDom);
Template(const Template& other);
Template& operator=(const Template& other);
@@ -47,10 +48,13 @@ namespace AzToolsFramework
virtual ~Template() noexcept = default;
bool IsValid() const;
bool IsLoadedWithErrors() const;
bool IsLoadedWithErrors() const;
void MarkAsLoadedWithErrors(bool loadedWithErrors);
bool IsDirty() const;
void MarkAsDirty(bool dirty);
bool AddLink(LinkId newLinkId);
bool RemoveLink(LinkId linkId);
bool HasLink(LinkId linkId) const;
@@ -64,7 +68,7 @@ namespace AzToolsFramework
PrefabDomValueReference GetInstancesValue();
PrefabDomValueConstReference GetInstancesValue() const;
const AZStd::string& GetFilePath() const;
const AZ::IO::Path& GetFilePath() const;
private:
// Container for keeping links representing the Template's nested instances.
@@ -74,10 +78,13 @@ namespace AzToolsFramework
PrefabDom m_prefabDom;
// File path of this Prefab Template.
AZStd::string m_filePath;
AZ::IO::Path m_filePath;
// Flag to tell if this Template and all its nested instances loaded with any error.
bool m_isLoadedWithErrors = false;
// Flag to tell if this Template has changes that have yet to be saved to file.
bool m_isDirty = false;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -20,6 +20,7 @@
#include <AzToolsFramework/ToolsComponents/EditorDisabledCompositionComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorInspectorComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorPendingCompositionComponent.h>
#include <AzToolsFramework/Undo/UndoCacheInterface.h>
#include "SliceMetadataEntityContextComponent.h"
@@ -196,10 +197,10 @@ namespace AzToolsFramework
SliceMetadataEntityContextNotificationBus::Broadcast(&SliceMetadataEntityContextNotifications::OnMetadataEntityAdded, metadataEntity.GetId());
// Register the metadata entity with the pre-emptive undo cache (if exists) so it has an initial state
auto* preemptiveUndoCache = PreemptiveUndoCache::Get();
if (preemptiveUndoCache)
auto undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
if (undoCacheInterface)
{
preemptiveUndoCache->UpdateCache(metadataEntity.GetId());
undoCacheInterface->UpdateCache(metadataEntity.GetId());
}
}
@@ -52,6 +52,15 @@ namespace AzToolsFramework
return true;
}
bool SourceControlThumbnailKey::Equals(const ThumbnailKey* other) const
{
if (!ThumbnailKey::Equals(other))
{
return false;
}
return m_fileName == azrtti_cast<const SourceControlThumbnailKey*>(other)->GetFileName();
}
//////////////////////////////////////////////////////////////////////////
// SourceControlThumbnail
//////////////////////////////////////////////////////////////////////////
@@ -142,7 +151,7 @@ namespace AzToolsFramework
// SourceControlThumbnailCache
//////////////////////////////////////////////////////////////////////////
SourceControlThumbnailCache::SourceControlThumbnailCache()
: ThumbnailCache<SourceControlThumbnail, SourceControlKeyHash, SourceControlKeyEqual>()
: ThumbnailCache<SourceControlThumbnail>()
{
}
@@ -36,6 +36,8 @@ namespace AzToolsFramework
bool UpdateThumbnail() override;
bool Equals(const ThumbnailKey* other) const override;
protected:
//! absolute path
AZStd::string m_fileName;
@@ -107,7 +109,7 @@ namespace AzToolsFramework
//! Stores products' thumbnails
class SourceControlThumbnailCache
: public ThumbnailCache<SourceControlThumbnail, SourceControlKeyHash, SourceControlKeyEqual>
: public ThumbnailCache<SourceControlThumbnail>
{
public:
SourceControlThumbnailCache();
@@ -39,6 +39,16 @@ namespace AzToolsFramework
return true;
}
size_t ThumbnailKey::GetHash() const
{
return 0;
}
bool ThumbnailKey::Equals(const ThumbnailKey* other) const
{
return RTTI_GetType() == other->RTTI_GetType();
}
//////////////////////////////////////////////////////////////////////////
// Thumbnail
//////////////////////////////////////////////////////////////////////////
@@ -49,19 +49,23 @@ namespace AzToolsFramework
virtual bool UpdateThumbnail();
Q_SIGNALS:
virtual size_t GetHash() const;
virtual bool Equals(const ThumbnailKey* other) const;
Q_SIGNALS:
//! Updated signal is dispatched whenever thumbnail data was changed. Anyone using this thumbnail should listen to this.
void ThumbnailUpdatedSignal() const;
//! Force update mapped thumbnails
void UpdateThumbnailSignal() const;
private:
bool m_ready = false;
};
typedef QSharedPointer<ThumbnailKey> SharedThumbnailKey;
#define MAKE_TKEY(type, ...) QSharedPointer<type>(new type(__VA_ARGS__))
#define MAKE_TKEY(type, ...) QSharedPointer<type>(new type(__VA_ARGS__))
//! Thumbnail is the base class in thumbnailer system.
/*
@@ -92,7 +96,7 @@ Q_SIGNALS:
SharedThumbnailKey GetKey() const;
State GetState() const;
Q_SIGNALS:
Q_SIGNALS:
void Updated() const;
public Q_SLOTS:
@@ -128,16 +132,44 @@ Q_SIGNALS:
};
typedef QSharedPointer<ThumbnailProvider> SharedThumbnailProvider;
}
}
namespace AZStd
{
// hash specialization
template <>
struct hash<AzToolsFramework::Thumbnailer::SharedThumbnailKey>
{
AZ_FORCE_INLINE size_t operator()(AzToolsFramework::Thumbnailer::SharedThumbnailKey key) const
{
return key->GetHash();
}
};
template <>
struct AZStd::equal_to<AzToolsFramework::Thumbnailer::SharedThumbnailKey>
{
AZ_FORCE_INLINE bool operator()(const AzToolsFramework::Thumbnailer::SharedThumbnailKey& left, const AzToolsFramework::Thumbnailer::SharedThumbnailKey& right) const
{
return left->Equals(right.data());
}
};
}
namespace AzToolsFramework
{
namespace Thumbnailer
{
//! ThumbnailCache manages thumbnails of specific type, derive your custom provider from this
/*
ThumbnailType - type of thumbnails managed
HasherType - hashing function for storing thumbnail keys in the hashtable
Hasher - hashing function for storing thumbnail keys in the hashtable
EqualKey - equality function for storing thumbnail keys in the hashtable
HasherType and EqualKey need to be provided on individual basis depending on
Hasher and EqualKey need to be provided on individual basis depending on
what constitutes a unique key and how should the key collection be optimized
*/
template<typename ThumbnailType, typename HasherType, typename EqualKey>
template<class ThumbnailType, class Hasher = AZStd::hash<SharedThumbnailKey>, class EqualKey = AZStd::equal_to<SharedThumbnailKey>>
class ThumbnailCache
: public ThumbnailProvider
, public AZ::TickBus::Handler
@@ -157,7 +189,7 @@ Q_SIGNALS:
protected:
int m_thumbnailSize;
AZStd::unordered_map<SharedThumbnailKey, SharedThumbnail, HasherType, EqualKey> m_cache;
AZStd::unordered_map<SharedThumbnailKey, SharedThumbnail, Hasher, EqualKey> m_cache;
//! Check if thumbnail key is handled by this provider, overload in derived class
virtual bool IsSupportedThumbnail(SharedThumbnailKey key) const = 0;
@@ -169,4 +201,5 @@ Q_SIGNALS:
Q_DECLARE_METATYPE(AzToolsFramework::Thumbnailer::SharedThumbnailKey)
#include <AzToolsFramework/Thumbnails/Thumbnail.inl>
@@ -16,21 +16,21 @@ namespace AzToolsFramework
{
namespace Thumbnailer
{
template <typename ThumbnailType, typename HasherType, typename EqualKey>
ThumbnailCache<ThumbnailType, HasherType, EqualKey>::ThumbnailCache()
template <class ThumbnailType, class Hasher, class EqualKey>
ThumbnailCache<ThumbnailType, Hasher, EqualKey>::ThumbnailCache()
: m_thumbnailSize(0)
{
BusConnect();
}
template <typename ThumbnailType, typename HasherType, typename EqualKey>
ThumbnailCache<ThumbnailType, HasherType, EqualKey>::~ThumbnailCache()
template <class ThumbnailType, class Hasher, class EqualKey>
ThumbnailCache<ThumbnailType, Hasher, EqualKey>::~ThumbnailCache()
{
BusDisconnect();
}
template <typename ThumbnailType, typename HasherType, typename EqualKey>
void ThumbnailCache<ThumbnailType, HasherType, EqualKey>::OnTick(float deltaTime, AZ::ScriptTimePoint /*time*/)
template <class ThumbnailType, class Hasher, class EqualKey>
void ThumbnailCache<ThumbnailType, Hasher, EqualKey>::OnTick(float deltaTime, AZ::ScriptTimePoint /*time*/)
{
for (auto& kvp : m_cache)
{
@@ -38,9 +38,8 @@ namespace AzToolsFramework
}
}
template <typename ThumbnailType, typename HasherType, typename EqualKey>
bool ThumbnailCache<ThumbnailType, HasherType, EqualKey>::GetThumbnail(
SharedThumbnailKey key, SharedThumbnail& thumbnail)
template <class ThumbnailType, class Hasher, class EqualKey>
bool ThumbnailCache<ThumbnailType, Hasher, EqualKey>::GetThumbnail(SharedThumbnailKey key, SharedThumbnail& thumbnail)
{
auto it = m_cache.find(key);
if (it != m_cache.end())
@@ -57,8 +56,8 @@ namespace AzToolsFramework
return false;
}
template <typename ThumbnailType, typename HasherType, typename EqualKey>
void ThumbnailCache<ThumbnailType, HasherType, EqualKey>::SetThumbnailSize(int thumbnailSize)
template <class ThumbnailType, class Hasher, class EqualKey>
void ThumbnailCache<ThumbnailType, Hasher, EqualKey>::SetThumbnailSize(int thumbnailSize)
{
m_thumbnailSize = thumbnailSize;
}
@@ -65,7 +65,7 @@ namespace AzToolsFramework
static const bool EnableEventQueue = true;
typedef AZStd::recursive_mutex MutexType;
virtual void RenderThumbnail(AZ::Data::AssetId assetId, int thumbnailSize) = 0;
virtual void RenderThumbnail(SharedThumbnailKey thumbnailKey, int thumbnailSize) = 0;
virtual bool Installed() const { return false; }
};
@@ -79,7 +79,7 @@ namespace AzToolsFramework
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef AZ::Data::AssetId BusIdType;
typedef SharedThumbnailKey BusIdType;
//! notify product thumbnail that the data is ready
virtual void ThumbnailRendered(QPixmap& thumbnailImage) = 0;
@@ -67,10 +67,7 @@ namespace AzToolsFramework
incompatible.push_back(AZ_CRC_CE("LookAtService"));
incompatible.push_back(AZ_CRC_CE("SequenceService"));
incompatible.push_back(AZ_CRC_CE("ClothMeshService"));
incompatible.push_back(AZ_CRC_CE("PhysXColliderService"));
incompatible.push_back(AZ_CRC_CE("PhysXTriggerService"));
incompatible.push_back(AZ_CRC_CE("PhysXJointService"));
incompatible.push_back(AZ_CRC_CE("PhysXShapeColliderService"));
incompatible.push_back(AZ_CRC_CE("PhysXCharacterControllerService"));
incompatible.push_back(AZ_CRC_CE("PhysXRagdollService"));
incompatible.push_back(AZ_CRC_CE("WhiteBoxService"));
@@ -35,6 +35,7 @@ namespace AzToolsFramework
public:
AZ_EDITOR_COMPONENT(ScriptEditorComponent, "{b5fc8679-fa2a-4c7c-ac42-dcc279ea613a}")
static void Reflect(AZ::ReflectContext* context);
static bool DoComponentsMatch(const ScriptEditorComponent* thisComponent, const ScriptEditorComponent* otherComponent);
ScriptEditorComponent() = default;
@@ -79,8 +80,6 @@ namespace AzToolsFramework
float m_sortOrder; // Sort order of the property as defined by using the "order" attribute, by default the order is FLT_MAX which means alphabetical sort will be used
};
static void Reflect(AZ::ReflectContext* context);
void LoadProperties();
// make sure internal script (m_scriptComponent.m_script) is set before loading
void LoadScript();
@@ -196,7 +196,6 @@ namespace AssetProcessor
AZ_UNUSED(autoRegisterIfNotFound);
AZ_Assert(autoRegisterIfNotFound == false, "Auto registration is invalid during asset processing.");
AZ_UNUSED(typeToRegister);
AZ_Assert(typeToRegister == AZ::Data::s_invalidAssetType, "Can not register types during asset processing.");
AzFramework::SocketConnection* engineConnection = AzFramework::SocketConnection::GetInstance();
if (!engineConnection || !engineConnection->IsConnected())
@@ -23,9 +23,11 @@
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/ToolsComponents/TransformComponentBus.h>
#include <AzToolsFramework/ToolsComponents/TransformScalePropertyHandler.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
@@ -1054,11 +1056,32 @@ namespace AzToolsFramework
AZ::u32 TransformComponent::ParentChanged()
{
AZ::u32 refreshLevel = AZ::Edit::PropertyRefreshLevels::None;
if (!m_parentEntityId.IsValid())
{
// If Prefabs are enabled, reroute the invalid id to the level root
bool isPrefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (isPrefabSystemEnabled)
{
auto prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get();
if (prefabPublicInterface)
{
m_parentEntityId = prefabPublicInterface->GetLevelInstanceContainerEntityId();
refreshLevel = AZ::Edit::PropertyRefreshLevels::ValuesOnly;
}
}
}
auto parentId = m_parentEntityId;
m_parentEntityId = m_previousParentEntityId;
SetParent(parentId);
return AZ::Edit::PropertyRefreshLevels::None;
return refreshLevel;
}
AZ::u32 TransformComponent::TransformChanged()
@@ -63,7 +63,12 @@ namespace AzToolsFramework
void EditorEntityUiHandlerBase::PaintDescendantBackground(QPainter* /*painter*/, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/,
const QModelIndex& /*descendantIndex*/) const
{
}
}
void EditorEntityUiHandlerBase::PaintDescendantBranchBackground(QPainter* /*painter*/, const QTreeView* /*view*/, const QRect& /*rect*/,
const QModelIndex& /*index*/, const QModelIndex& /*descendantIndex*/) const
{
}
void EditorEntityUiHandlerBase::PaintItemForeground(QPainter* /*painter*/, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const
{
@@ -20,6 +20,7 @@
#include <QStyleOptionViewItem>
class QPainter;
class QTreeView;
namespace AzToolsFramework
{
@@ -50,6 +51,9 @@ namespace AzToolsFramework
//! Paints the background of the descendants of the item in the Outliner.
virtual void PaintDescendantBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index,
const QModelIndex& descendantIndex) const;
//! Paints the background of the descendant branches of the item in the Outliner.
virtual void PaintDescendantBranchBackground(QPainter* painter, const QTreeView* view, const QRect& rect,
const QModelIndex& index, const QModelIndex& descendantIndex) const;
//! Paints visual elements on the foreground of the item in the Outliner.
virtual void PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
@@ -21,29 +21,17 @@
namespace AzToolsFramework
{
const int LayerUiHandler::m_layerSquareSize = 20;
const int LayerUiHandler::m_layerStripeWidth = 1;
const int LayerUiHandler::m_layerDividerLineHeight = 1;
const int LayerUiHandler::m_lastEntityInLayerDividerLineHeight = 1;
const QColor LayerUiHandler::m_layerBackgroundColor = QColor("#2F2F2F");
const QColor LayerUiHandler::m_layerDescendantBackgroundColor = QColor("#333333");
const QColor LayerUiHandler::m_layerBorderTopColor = QColor("#515151");
const QColor LayerUiHandler::m_layerBorderBottomColor = QColor("#252525");
const QString LayerUiHandler::m_layerIconPath = QString(":/Icons/layer_icon.svg");
const QString LayerUiHandler::m_layerIconPath = QString(":/Entity/layer.svg");
QString LayerUiHandler::GenerateItemInfoString(AZ::EntityId entityId) const
{
QString result;
bool isLayerEntity = false;
AZ::Outcome<AZStd::string, AZStd::string> layerBaseNameResult;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerEntity,
entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
if (!isLayerEntity)
{
return result;
}
layerBaseNameResult, entityId, &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::GetLayerBaseFileName);
bool hasUnsavedLayerChanges = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
@@ -51,16 +39,28 @@ namespace AzToolsFramework
entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasUnsavedChanges);
QString result = "<span style=\"font-style: italic; font-weight: 400;\">";
if (layerBaseNameResult.IsSuccess())
{
result += QString("(%1.layer").arg(layerBaseNameResult.GetValue().c_str());
}
if (hasUnsavedLayerChanges)
{
result = QObject::tr("*");
result += QString("*");
}
if (layerBaseNameResult.IsSuccess())
{
result += QString(")");
}
result += "</span>";
bool isLayerNameValid = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerNameValid,
entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::IsLayerNameValid);
isLayerNameValid, entityId, &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::IsLayerNameValid);
if (!isLayerNameValid)
{
@@ -75,22 +75,7 @@ namespace AzToolsFramework
return QPixmap(m_layerIconPath);
}
void LayerUiHandler::PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const
{
QPainterPath backgroundPath;
backgroundPath.addRect(option.rect);
painter->fillPath(backgroundPath, m_layerBackgroundColor);
}
void LayerUiHandler::PaintDescendantBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& /*index*/,
const QModelIndex& /*descendantIndex*/) const
{
QPainterPath backgroundPath;
backgroundPath.addRect(option.rect);
painter->fillPath(backgroundPath, m_layerDescendantBackgroundColor);
}
void LayerUiHandler::PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
void LayerUiHandler::PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
@@ -99,11 +84,18 @@ namespace AzToolsFramework
return;
}
painter->save();
painter->setRenderHint(QPainter::Antialiasing, false);
// Dark Grey Background
QPainterPath backgroundPath;
backgroundPath.addRect(option.rect);
painter->fillPath(backgroundPath, m_layerBackgroundColor);
// Left rect with the layer color
QColor layerColor;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerColor,
entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::GetLayerColor);
layerColor, entityId, &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::GetLayerColor);
const QTreeView* outlinerTreeView(qobject_cast<const QTreeView*>(option.widget));
int indentation = outlinerTreeView->indentation();
@@ -114,39 +106,31 @@ namespace AzToolsFramework
if (isFirstColumn)
{
// Layer colored box
QPainterPath layerIconPath;
QPainterPath iconBackgroundPath;
QPoint layerBoxOffset(0, (option.rect.height() - m_layerSquareSize) / 2);
QRect layerIconRect(option.rect.topLeft() + layerBoxOffset, QSize(m_layerSquareSize, m_layerSquareSize));
layerIconPath.addRect(layerIconRect);
painter->fillPath(layerIconPath, layerColor);
iconBackgroundPath.addRect(layerIconRect);
painter->fillPath(iconBackgroundPath, layerColor);
// Left border
PaintLayerStripeAndBorder(
painter,
option.rect.left(),
option.rect.top(),
option.rect.bottom(),
m_layerBorderBottomColor,
layerColor);
painter, option.rect.left() - 1, option.rect.top(), option.rect.bottom(), m_layerBorderBottomColor, layerColor);
}
QModelIndex nameColumn = index.sibling(index.row(), EntityOutlinerListModel::Column::ColumnName);
QModelIndex sibling = index.sibling(index.row() + 1, index.column());
painter->save();
painter->setRenderHint(QPainter::Antialiasing, false);
QPoint lineBottomLeft(option.rect.bottomLeft());
QPoint lineTopLeft(option.rect.topLeft());
if (isFirstColumn)
{
lineTopLeft.setX(lineTopLeft.x() + m_layerStripeWidth);
lineTopLeft.setX(lineTopLeft.x() + m_layerStripeWidth - 1);
}
QPen topLinePen(m_layerBorderTopColor, m_layerDividerLineHeight);
painter->setPen(topLinePen);
painter->drawLine(lineTopLeft, option.rect.topRight());
if (isFirstColumn)
{
lineBottomLeft.setX(lineBottomLeft.x());
@@ -156,11 +140,43 @@ namespace AzToolsFramework
lineBottomLeft.setX(lineBottomLeft.x() + m_layerStripeWidth);
}
}
QPen bottomLinePen(m_layerBorderBottomColor, m_layerDividerLineHeight);
painter->setPen(bottomLinePen);
painter->drawLine(lineBottomLeft, option.rect.bottomRight());
painter->restore();
}
void LayerUiHandler::PaintDescendantBackground(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& /*index*/, const QModelIndex& /*descendantIndex*/) const
{
QPainterPath backgroundPath;
backgroundPath.addRect(option.rect);
painter->fillPath(backgroundPath, m_layerDescendantBackgroundColor);
}
void LayerUiHandler::PaintDescendantBranchBackground(QPainter* painter, const QTreeView* view, const QRect& rect,
const QModelIndex& index, const QModelIndex& /*descendantIndex*/) const
{
if (!painter)
{
return;
}
painter->save();
painter->setRenderHint(QPainter::Antialiasing, false);
QPainterPath backgroundPath;
const int ancestorLeft = view->visualRect(index).left();
// Find the rect that extends fully to the left
QRect fullRect = rect;
fullRect.setLeft(ancestorLeft);
backgroundPath.addRect(fullRect);
painter->fillPath(backgroundPath, m_layerDescendantBackgroundColor);
painter->restore();
}
@@ -182,7 +198,7 @@ namespace AzToolsFramework
const QTreeView* outlinerTreeView(qobject_cast<const QTreeView*>(option.widget));
bool isFirstColumn = descendantIndex.column() == EntityOutlinerListModel::ColumnName;
bool isLastColumn = descendantIndex.column() == EntityOutlinerListModel::ColumnLockToggle;
int ancestorLeft = outlinerTreeView->visualRect(index).left();
int ancestorLeft = outlinerTreeView->visualRect(index).left() - 1;
// Draw the left stripe for this layer on the descendant's rect
if (isFirstColumn)
@@ -32,7 +32,8 @@ namespace AzToolsFramework
void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
void PaintDescendantBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index,
const QModelIndex& descendantIndex) const override;
void PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
void PaintDescendantBranchBackground(QPainter* painter, const QTreeView* view, const QRect& rect,
const QModelIndex& index, const QModelIndex& descendantIndex) const override;
void PaintDescendantForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index,
const QModelIndex& descendantIndex) const override;
@@ -43,12 +44,16 @@ namespace AzToolsFramework
static QModelIndex GetLastVisibleChild(const QModelIndex& parent);
static QModelIndex Internal_GetLastVisibleChild(const QAbstractItemModel* model, const QModelIndex& index);
static const int m_layerSquareSize;
static const int m_layerStripeWidth;
static const int m_layerDividerLineHeight;
static const int m_lastEntityInLayerDividerLineHeight;
static constexpr int m_layerSquareSize = 22;
static constexpr int m_layerStripeWidth = 1;
static constexpr int m_layerDividerLineHeight = 1;
static constexpr int m_lastEntityInLayerDividerLineHeight = 1;
static const QColor m_layerBackgroundColor;
static const QColor m_layerBackgroundHoveredColor;
static const QColor m_layerBackgroundSelectedColor;
static const QColor m_layerDescendantBackgroundColor;
static const QColor m_layerDescendantBackgroundHoveredColor;
static const QColor m_layerDescendantBackgroundSelectedColor;
static const QColor m_layerBorderTopColor;
static const QColor m_layerBorderBottomColor;
static const QString m_layerIconPath;
@@ -44,11 +44,11 @@ AzToolsFramework--EntityOutlinerCheckBox
border: 0px solid transparent;
border-radius: 0px;
spacing: 0px;
padding: 0px;
padding: 0;
padding-right: 2px;
line-height: 0px;
font-size: 0px;
margin: 0px;
background-color: transparent;
max-height: 20px;
max-width: 18px;
}
@@ -58,14 +58,15 @@ AzToolsFramework--EntityOutlinerCheckBox::indicator
width: 16px;
height: 16px;
image-position:center;
border: 1px solid transparent;
image: none;
spacing: 0px;
padding: 0px;
line-height: 0px;
font-size: 0px;
margin: 0;
margin: 3px 0 0 0;
max-width: 18px;
width: 18px;
}
AzToolsFramework--EntityOutlinerCheckBox#VisibilityMixed::indicator:checked
@@ -83,7 +84,6 @@ AzToolsFramework--EntityOutlinerCheckBox#VisibilityMixed::indicator:checked
{
background: rgba(0, 0, 0, 80);
border-radius: 5px;
padding-bottom: 1px;
}
@@ -99,6 +99,7 @@ AzToolsFramework--EntityOutlinerCheckBox#Visibility::indicator:unchecked
, AzToolsFramework--EntityOutlinerCheckBox#VisibilityMixed::indicator:unchecked
{
image: url(:/visibility_on.svg);
margin-top: 3px;
}
AzToolsFramework--EntityOutlinerCheckBox#VisibilityOverridden::indicator:checked
@@ -109,6 +110,7 @@ AzToolsFramework--EntityOutlinerCheckBox#VisibilityOverridden::indicator:checked
AzToolsFramework--EntityOutlinerCheckBox#VisibilityOverridden::indicator:unchecked
{
image: url(:/visibility_on_transparent.svg);
margin-top: 3px;
}
AzToolsFramework--EntityOutlinerCheckBox#VisibilityHover::indicator:checked
@@ -116,6 +118,7 @@ AzToolsFramework--EntityOutlinerCheckBox#VisibilityHover::indicator:checked
, AzToolsFramework--EntityOutlinerCheckBox#VisibilityOverriddenHover::indicator:checked
{
image: url(:/visibility_default_hover.svg);
margin-top: 3px;
}
AzToolsFramework--EntityOutlinerCheckBox#VisibilityHover::indicator:unchecked
@@ -123,6 +126,7 @@ AzToolsFramework--EntityOutlinerCheckBox#VisibilityHover::indicator:unchecked
, AzToolsFramework--EntityOutlinerCheckBox#VisibilityOverriddenHover::indicator:unchecked
{
image: url(:/visibility_on_hover.svg);
margin-top: 3px;
}
@@ -132,6 +136,7 @@ AzToolsFramework--EntityOutlinerCheckBox#Lock::indicator:checked
, AzToolsFramework--EntityOutlinerCheckBox#LockMixed::indicator:checked
{
image: url(:/lock_on.svg);
margin-top: 1px;
}
AzToolsFramework--EntityOutlinerCheckBox#Lock::indicator:unchecked
@@ -148,6 +153,7 @@ AzToolsFramework--EntityOutlinerCheckBox#LockOverridden::indicator:checked
AzToolsFramework--EntityOutlinerCheckBox#LockOverridden::indicator:unchecked
{
image: url(:/lock_default_transparent.svg);
margin-top: 1px;
}
AzToolsFramework--EntityOutlinerCheckBox#LockHover::indicator:checked
@@ -155,6 +161,7 @@ AzToolsFramework--EntityOutlinerCheckBox#LockHover::indicator:checked
, AzToolsFramework--EntityOutlinerCheckBox#LockOverriddenHover::indicator:checked
{
image: url(:/lock_on_hover.svg);
margin-top: 1px;
}
AzToolsFramework--EntityOutlinerCheckBox#LockHover::indicator:unchecked
@@ -162,4 +169,5 @@ AzToolsFramework--EntityOutlinerCheckBox#LockHover::indicator:unchecked
, AzToolsFramework--EntityOutlinerCheckBox#LockOverriddenHover::indicator:unchecked
{
image: url(:/lock_default_hover.svg);
margin-top: 1px;
}
@@ -1970,6 +1970,13 @@ namespace AzToolsFramework
void EntityOutlinerItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
// Customize option to prevent Qt from painting the default focus rectangle
QStyleOptionViewItem customOption{option};
if (customOption.state & QStyle::State_HasFocus)
{
customOption.state ^= QStyle::State_HasFocus;
}
// Retrieve the Entity UI Handler
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
auto entityUiHandler = m_editorEntityFrameworkInterface->GetHandler(entityId);
@@ -1977,6 +1984,9 @@ namespace AzToolsFramework
const bool isSelected = (option.state & QStyle::State_Selected);
const bool isHovered = (option.state & QStyle::State_MouseOver) && (option.state & QStyle::State_Enabled);
// Paint the Selection/Hover Rect
PaintSelectionHoverRect(painter, option, index, isSelected, isHovered);
// Paint Ancestor Backgrounds
PaintAncestorBackgrounds(painter, option, index);
@@ -1986,25 +1996,6 @@ namespace AzToolsFramework
entityUiHandler->PaintItemBackground(painter, option, index);
}
// Paint the Selection/Hover Rect
PaintSelectionHoverRect(painter, option, index, isSelected, isHovered);
// Paint Ancestor Foregrounds
PaintAncestorForegrounds(painter, option, index);
// Paint the Foreground
if (entityUiHandler != nullptr)
{
entityUiHandler->PaintItemForeground(painter, option, index);
}
// Customize option to prevent Qt from painting the default focus rectangle
QStyleOptionViewItem customOption{ option };
if (customOption.state & QStyle::State_HasFocus)
{
customOption.state ^= QStyle::State_HasFocus;
}
switch (index.column())
{
case EntityOutlinerListModel::ColumnVisibilityToggle:
@@ -2033,6 +2024,15 @@ namespace AzToolsFramework
}
break;
}
// Paint Ancestor Foregrounds
PaintAncestorForegrounds(painter, option, index);
// Paint the Foreground
if (entityUiHandler != nullptr)
{
entityUiHandler->PaintItemForeground(painter, option, index);
}
}
void EntityOutlinerItemDelegate::PaintAncestorBackgrounds(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
@@ -2069,17 +2069,18 @@ namespace AzToolsFramework
if (isSelected || isHovered)
{
QPainterPath layerBGPath;
QRect layerBGRect(option.rect);
layerBGPath.addRect(layerBGRect);
QPainterPath backgroundPath;
QRect backgroundRect(option.rect);
QColor layerBG = m_hoverColor;
backgroundPath.addRect(backgroundRect);
QColor backgroundColor = m_hoverColor;
if (isSelected)
{
layerBG = m_selectedColor;
backgroundColor = m_selectedColor;
}
painter->fillPath(layerBGPath, layerBG);
painter->fillPath(backgroundPath, backgroundColor);
}
painter->restore();
@@ -2212,13 +2213,9 @@ namespace AzToolsFramework
textWidthAvailable -= fontMetrics.horizontalAdvance(QObject::tr("..."));
if (!infoString.isEmpty())
{
// The info string includes HTML markup, which can cause an issue computing the width.
// The markup on the text is light (just color, italic or bold), so an approximate width
// is computed by taking the width of the non-HTML portion of the string and padding it a bit.
QString htmlStripped = infoString;
htmlStripped.remove(htmlMarkupRegex);
const float layerInfoPadding = 1.2f;
textWidthAvailable -= fontMetrics.horizontalAdvance(htmlStripped) * layerInfoPadding;
textWidthAvailable -= fontMetrics.horizontalAdvance(htmlStripped) + 5;
}
entityNameRichText = fontMetrics.elidedText(optionV4.text, Qt::TextElideMode::ElideRight, textWidthAvailable);
@@ -2226,7 +2223,12 @@ namespace AzToolsFramework
if (!infoString.isEmpty())
{
entityNameRichText = QObject::tr("%1%2").arg(entityNameRichText).arg(infoString);
entityNameRichText = QString("<table width=\"100%\" style=\"border-collapse: collapse; border-spacing: 0;\">")
+ QString("<tr><td>")
+ QString(entityNameRichText)
+ QString("</td><td align=\"right\">")
+ QString(infoString)
+ QString("</td></tr></table>");
}
// delete the text from the item so we can use the standard painter to draw the icon
@@ -2238,7 +2240,7 @@ namespace AzToolsFramework
textDoc.setDefaultFont(optionV4.font);
textDoc.setDefaultStyleSheet("body {color: white}");
textDoc.setHtml("<body>" + entityNameRichText + "</body>");
painter->translate(textRect.topLeft() + QPoint(0, -1));
painter->translate(textRect.topLeft());
textDoc.setTextWidth(textRect.width());
textDoc.drawContents(painter, QRectF(0, 0, textRect.width(), textRect.height()));
@@ -2246,7 +2248,7 @@ namespace AzToolsFramework
EntityOutlinerListModel::s_paintingName = false;
}
QSize EntityOutlinerItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
QSize EntityOutlinerItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const
{
// Get the height of a tall character...
// we do this only once per 'tick'
@@ -2262,14 +2264,7 @@ namespace AzToolsFramework
QTimer::singleShot(0, resetFunction);
}
QSize sh = QSize(0, m_cachedBoundingRectOfTallCharacter.height() + EntityOutlinerListModel::s_OutlinerSpacing);
if (index.column() == EntityOutlinerListModel::ColumnVisibilityToggle || index.column() == EntityOutlinerListModel::ColumnLockToggle)
{
sh.setWidth(m_toggleColumnWidth);
}
return sh;
return QSize(0, m_cachedBoundingRectOfTallCharacter.height() + EntityOutlinerListModel::s_OutlinerSpacing);
}
bool EntityOutlinerItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
@@ -107,7 +107,7 @@ namespace AzToolsFramework
};
// Spacing is appropriate and matches the outliner concept work from the UI team.
static const int s_OutlinerSpacing = 5;
static const int s_OutlinerSpacing = 7;
static bool s_paintingName;
@@ -358,8 +358,6 @@ namespace AzToolsFramework
mutable CheckboxGroup m_visibilityCheckBoxes;
mutable CheckboxGroup m_lockCheckBoxes;
const int m_toggleColumnWidth = 16;
// this is a cache, and is hence mutable
mutable QRect m_cachedBoundingRectOfTallCharacter;
@@ -183,8 +183,6 @@ namespace AzToolsFramework
// Paint the branch background as defined by the entity's handler, or its closes ancestor's.
PaintBranchBackground(painter, rect, index);
// Make sure the base class is called after the layer rect is drawn,
// so that the foldout arrow draws on top of the layer color.
QTreeView::drawBranches(painter, rect, index);
}
@@ -209,12 +207,7 @@ namespace AzToolsFramework
if (ancestorUiHandler != nullptr)
{
int ancestorLeft = visualRect(ancestorIndex).left();
QStyleOptionViewItem option;
option.rect = rect;
option.rect.setLeft(ancestorLeft);
ancestorUiHandler->PaintDescendantBackground(painter, option, ancestorIndex, index);
ancestorUiHandler->PaintDescendantBranchBackground(painter, this, rect, ancestorIndex, index);
}
}
}
@@ -171,7 +171,7 @@ namespace AzToolsFramework
const int autoExpandDelayMilliseconds = 2500;
m_gui->m_objectTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_gui->m_objectTree->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
m_gui->m_objectTree->setEditTriggers(QAbstractItemView::EditKeyPressed);
m_gui->m_objectTree->setAutoExpandDelay(autoExpandDelayMilliseconds);
m_gui->m_objectTree->setDragEnabled(true);
m_gui->m_objectTree->setDropIndicatorShown(true);
@@ -179,9 +179,11 @@ namespace AzToolsFramework
m_gui->m_objectTree->setDragDropOverwriteMode(false);
m_gui->m_objectTree->setDragDropMode(QAbstractItemView::DragDrop);
m_gui->m_objectTree->setDefaultDropAction(Qt::CopyAction);
m_gui->m_objectTree->setExpandsOnDoubleClick(false);
m_gui->m_objectTree->setContextMenuPolicy(Qt::CustomContextMenu);
m_gui->m_objectTree->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_gui->m_objectTree->setAutoScrollMargin(20);
m_gui->m_objectTree->setIndentation(24);
connect(m_gui->m_objectTree, &QTreeView::customContextMenuRequested, this, &EntityOutlinerWidget::OnOpenTreeContextMenu);
// custom item delegate
@@ -193,6 +195,7 @@ namespace AzToolsFramework
// Link up signals for informing the model of tree changes using the proxy as an intermediary
connect(m_gui->m_objectTree, &QTreeView::clicked, this, &EntityOutlinerWidget::OnTreeItemClicked);
connect(m_gui->m_objectTree, &QTreeView::doubleClicked, this, &EntityOutlinerWidget::OnTreeItemDoubleClicked);
connect(m_gui->m_objectTree, &QTreeView::expanded, this, &EntityOutlinerWidget::OnTreeItemExpanded);
connect(m_gui->m_objectTree, &QTreeView::collapsed, this, &EntityOutlinerWidget::OnTreeItemCollapsed);
connect(m_gui->m_objectTree, &EntityOutlinerTreeView::ItemDropped, this, &EntityOutlinerWidget::OnDropEvent);
@@ -223,8 +226,12 @@ namespace AzToolsFramework
m_gui->m_objectTree->header()->setStretchLastSection(false);
// resize the icon columns so that the Visibility and Lock toggle icon columns stay right-justified
m_gui->m_objectTree->header()->setStretchLastSection(false);
m_gui->m_objectTree->header()->setMinimumSectionSize(0);
m_gui->m_objectTree->header()->setSectionResizeMode(EntityOutlinerListModel::ColumnName, QHeaderView::Stretch);
m_gui->m_objectTree->header()->setSectionResizeMode(EntityOutlinerListModel::ColumnVisibilityToggle, QHeaderView::ResizeToContents);
m_gui->m_objectTree->header()->setSectionResizeMode(EntityOutlinerListModel::ColumnVisibilityToggle, QHeaderView::Fixed);
m_gui->m_objectTree->header()->resizeSection(EntityOutlinerListModel::ColumnVisibilityToggle, 20);
m_gui->m_objectTree->header()->setSectionResizeMode(EntityOutlinerListModel::ColumnLockToggle, QHeaderView::Fixed);
m_gui->m_objectTree->header()->resizeSection(EntityOutlinerListModel::ColumnLockToggle, 24);
connect(m_gui->m_objectTree->selectionModel(),
@@ -271,10 +278,12 @@ namespace AzToolsFramework
ComponentModeFramework::EditorComponentModeNotificationBus::Handler::BusConnect(
GetEntityContextId());
EditorEntityInfoNotificationBus::Handler::BusConnect();
AZ::Interface<EntityOutlinerWidgetInterface>::Register(this);
}
EntityOutlinerWidget::~EntityOutlinerWidget()
{
AZ::Interface<EntityOutlinerWidgetInterface>::Unregister(this);
ComponentModeFramework::EditorComponentModeNotificationBus::Handler::BusDisconnect();
EditorEntityInfoNotificationBus::Handler::BusDisconnect();
EditorPickModeNotificationBus::Handler::BusDisconnect();
@@ -798,6 +807,8 @@ namespace AzToolsFramework
#ifdef Q_OS_MAC
// "Alt+Return" translates to Option+Return on macOS
m_actionToRenameSelection->setShortcut(tr("Alt+Return"));
#elseif Q_OS_WIN
m_actionToRenameSelection->setShortcut(tr("F2"));
#endif
m_actionToRenameSelection->setShortcutContext(Qt::WidgetWithChildrenShortcut);
connect(m_actionToRenameSelection, &QAction::triggered, this, &EntityOutlinerWidget::DoRenameSelection);
@@ -860,6 +871,10 @@ namespace AzToolsFramework
}
}
void EntityOutlinerWidget::OnTreeItemDoubleClicked(const QModelIndex& /*index*/)
{
}
void EntityOutlinerWidget::OnTreeItemExpanded(const QModelIndex& index)
{
m_listModel->OnEntityExpanded(GetEntityIdFromIndex(index));
@@ -1075,6 +1090,30 @@ namespace AzToolsFramework
SetEntityOutlinerState(m_gui, true);
}
void EntityOutlinerWidget::SetRootEntity(AZ::EntityId rootEntityId)
{
// The proxy model needs a tick to initialize, else it will return an invalid index in mapFromSource.
QTimer::singleShot(0, this, [rootEntityId, this]() {
QModelIndex rootIndex = m_listModel->GetIndexFromEntity(rootEntityId);
QModelIndex proxyIndex = m_proxyModel->mapFromSource(rootIndex);
m_gui->m_objectTree->setRootIndex(proxyIndex);
});
}
void EntityOutlinerWidget::SetUpdatesEnabled(bool enable)
{
if (enable)
{
QTimer::singleShot(1, this, [this]() {
m_gui->m_objectTree->setUpdatesEnabled(true);
});
}
else
{
m_gui->m_objectTree->setUpdatesEnabled(false);
}
}
void EntityOutlinerWidget::OnEntityInfoUpdatedAddChildEnd(AZ::EntityId /*parentId*/, AZ::EntityId childId)
{
QueueContentUpdateSort(childId);
@@ -21,8 +21,9 @@
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/ToolsMessaging/EntityHighlightBus.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerSearchWidget.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerCacheBus.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerSearchWidget.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerWidgetInterface.h>
#include <AzToolsFramework/UI/SearchWidget/SearchWidgetTypes.hxx>
#include <QIcon>
@@ -60,12 +61,13 @@ namespace AzToolsFramework
, private EditorEntityContextNotificationBus::Handler
, private EditorEntityInfoNotificationBus::Handler
, private ComponentModeFramework::EditorComponentModeNotificationBus::Handler
, private EntityOutlinerWidgetInterface
{
Q_OBJECT;
public:
AZ_CLASS_ALLOCATOR(EntityOutlinerWidget, AZ::SystemAllocator, 0)
EntityOutlinerWidget(QWidget* pParent = NULL, Qt::WindowFlags flags = Qt::WindowFlags());
explicit EntityOutlinerWidget(QWidget* pParent = NULL, Qt::WindowFlags flags = Qt::WindowFlags());
virtual ~EntityOutlinerWidget();
private Q_SLOTS:
@@ -103,6 +105,10 @@ namespace AzToolsFramework
void EnteredComponentMode(const AZStd::vector<AZ::Uuid>& componentModeTypes) override;
void LeftComponentMode(const AZStd::vector<AZ::Uuid>& componentModeTypes) override;
// EntityOutlinerWidgetInterface
void SetRootEntity(AZ::EntityId rootEntityId) override;
void SetUpdatesEnabled(bool enable) override;
// Build a selection object from the given entities. Entities already in the Widget's selection buffers are ignored.
template <class EntityIdCollection>
QItemSelection BuildSelectionFromEntities(const EntityIdCollection& entityIds);
@@ -138,6 +144,7 @@ namespace AzToolsFramework
QAction* m_actionGoToEntitiesInViewport;
void OnTreeItemClicked(const QModelIndex& index);
void OnTreeItemDoubleClicked(const QModelIndex& index);
void OnTreeItemExpanded(const QModelIndex& index);
void OnTreeItemCollapsed(const QModelIndex& index);
void OnExpandEntity(const AZ::EntityId& entityId, bool expand);
@@ -0,0 +1,30 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzToolsFramework
{
class EntityOutlinerWidgetInterface
{
public:
AZ_RTTI(EntityOutlinerWidgetInterface, "{30C0F252-EC84-4196-BF59-EB9E73B8ADCB}");
virtual void SetRootEntity(AZ::EntityId rootEntityId) = 0;
virtual void SetUpdatesEnabled(bool enable) = 0;
};
} // namespace AzToolsFramework
@@ -0,0 +1,35 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Entity.h>
namespace AzToolsFramework
{
namespace Prefab
{
class PrefabInstanceContainerNotifications
: public AZ::EBusTraits
{
public:
virtual ~PrefabInstanceContainerNotifications() = default;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual void OnPrefabComponentActivate(AZ::EntityId entityId) = 0;
virtual void OnPrefabComponentDeactivate(AZ::EntityId entityId) = 0;
};
using PrefabInstanceContainerNotificationBus = AZ::EBus<PrefabInstanceContainerNotifications>;
}
}
@@ -0,0 +1,39 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzToolsFramework
{
namespace Prefab
{
class PrefabIntegrationInterface
{
public:
AZ_RTTI(PrefabIntegrationInterface, "{B88AE045-7711-49BC-8336-45003D1C9116}");
/**
* Create a new entity at the position provided.
* @param position The position the new entity will be created at.
* @param parentId The id of the parent of the newly created entity.
* @return The id of the newly created entity.
*/
virtual AZ::EntityId CreateNewEntityAtPosition(const AZ::Vector3& position, AZ::EntityId parentId) = 0;
};
} // namespace Prefab
} // namespace AzToolsFramework

Some files were not shown because too many files have changed in this diff Show More