Merge branch 'development' into LYN-5265_state_tracker_impl

This commit is contained in:
John
2021-10-11 08:38:54 +01:00
502 changed files with 16957 additions and 11088 deletions
@@ -69,6 +69,7 @@
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiSystemComponent.h>
#include <AzToolsFramework/Undo/UndoCacheInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <Entity/EntityUtilityComponent.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'
@@ -271,7 +272,8 @@ namespace AzToolsFramework
azrtti_typeid<AzToolsFramework::EditorInteractionSystemComponent>(),
azrtti_typeid<Components::EditorEntitySearchComponent>(),
azrtti_typeid<Components::EditorIntersectorComponent>(),
azrtti_typeid<AzToolsFramework::SliceRequestComponent>()
azrtti_typeid<AzToolsFramework::SliceRequestComponent>(),
azrtti_typeid<AzToolsFramework::EntityUtilityComponent>()
});
return components;
@@ -410,7 +410,7 @@ namespace AzToolsFramework
filter.append(ext);
if (i < n - 1)
{
filter.append(", ");
filter.append(" ");
}
}
filter.append(")");
@@ -53,6 +53,7 @@
#include <AzToolsFramework/Thumbnails/ThumbnailerNullComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemComponent.h>
#include <AzToolsFramework/Entity/EntityUtilityComponent.h>
AZ_DEFINE_BUDGET(AzToolsFramework);
@@ -71,6 +72,7 @@ namespace AzToolsFramework
Components::EditorSelectionAccentSystemComponent::CreateDescriptor(),
EditorEntityContextComponent::CreateDescriptor(),
EditorEntityFixupComponent::CreateDescriptor(),
EntityUtilityComponent::CreateDescriptor(),
ContainerEntitySystemComponent::CreateDescriptor(),
FocusModeSystemComponent::CreateDescriptor(),
SliceMetadataEntityContextComponent::CreateDescriptor(),
@@ -0,0 +1,351 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <sstream>
#include <AzCore/JSON/rapidjson.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/JsonSerializationSettings.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzFramework/FileFunc/FileFunc.h>
#include <AzToolsFramework/Entity/EntityUtilityComponent.h>
#include <Entity/EditorEntityContextBus.h>
#include <rapidjson/document.h>
namespace AzToolsFramework
{
void ComponentDetails::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ComponentDetails>()
->Field("TypeInfo", &ComponentDetails::m_typeInfo)
->Field("BaseClasses", &ComponentDetails::m_baseClasses);
serializeContext->RegisterGenericType<AZStd::vector<ComponentDetails>>();
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<ComponentDetails>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "entity")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Property("TypeInfo", BehaviorValueProperty(&ComponentDetails::m_typeInfo))
->Property("BaseClasses", BehaviorValueProperty(&ComponentDetails::m_baseClasses))
->Method("__repr__", [](const ComponentDetails& obj)
{
std::ostringstream result;
bool first = true;
for (const auto& baseClass : obj.m_baseClasses)
{
if (!first)
{
result << ", ";
}
first = false;
result << baseClass.c_str();
}
return AZStd::string::format("%s, Base Classes: <%s>", obj.m_typeInfo.c_str(), result.str().c_str());
})
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString);
}
}
AZ::EntityId EntityUtilityComponent::CreateEditorReadyEntity(const AZStd::string& entityName)
{
auto* newEntity = m_entityContext->CreateEntity(entityName.c_str());
if (!newEntity)
{
AZ_Error("EditorEntityUtility", false, "Failed to create new entity %s", entityName.c_str());
return AZ::EntityId();
}
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, *newEntity);
newEntity->Init();
auto newEntityId = newEntity->GetId();
m_createdEntities.emplace_back(newEntityId);
return newEntityId;
}
AZ::TypeId GetComponentTypeIdFromName(const AZStd::string& typeName)
{
// Try to create a TypeId first. We won't show any warnings if this fails as the input might be a class name instead
AZ::TypeId typeId = AZ::TypeId::CreateStringPermissive(typeName.data());
// If the typeId is null, try a lookup by class name
if (typeId.IsNull())
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
auto typeNameCrc = AZ::Crc32(typeName.data());
auto typeUuidList = serializeContext->FindClassId(typeNameCrc);
// TypeId is invalid or class name is invalid
if (typeUuidList.empty())
{
AZ_Error("EntityUtilityComponent", false, "Provided type %s is either an invalid TypeId or does not match any class names", typeName.c_str());
return AZ::TypeId::CreateNull();
}
typeId = typeUuidList[0];
}
return typeId;
}
AZ::Component* FindComponentHelper(AZ::EntityId entityId, const AZ::TypeId& typeId, AZ::ComponentId componentId, bool createComponent = false)
{
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
if (!entity)
{
AZ_Error("EntityUtilityComponent", false, "Invalid entityId %s", entityId.ToString().c_str());
return nullptr;
}
AZ::Component* component = nullptr;
if (componentId != AZ::InvalidComponentId)
{
component = entity->FindComponent(componentId);
}
else
{
component = entity->FindComponent(typeId);
}
if (!component && createComponent)
{
component = entity->CreateComponent(typeId);
}
if (!component)
{
AZ_Error(
"EntityUtilityComponent", false, "Failed to find component (%s) on entity %s (%s)",
componentId != AZ::InvalidComponentId ? AZStd::to_string(componentId).c_str()
: typeId.ToString<AZStd::string>().c_str(),
entityId.ToString().c_str(),
entity->GetName().c_str());
return nullptr;
}
return component;
}
AzFramework::BehaviorComponentId EntityUtilityComponent::GetOrAddComponentByTypeName(AZ::EntityId entityId, const AZStd::string& typeName)
{
AZ::TypeId typeId = GetComponentTypeIdFromName(typeName);
if (typeId.IsNull())
{
return AzFramework::BehaviorComponentId(AZ::InvalidComponentId);
}
AZ::Component* component = FindComponentHelper(entityId, typeId, AZ::InvalidComponentId, true);
return component ? AzFramework::BehaviorComponentId(component->GetId()) :
AzFramework::BehaviorComponentId(AZ::InvalidComponentId);
}
bool EntityUtilityComponent::UpdateComponentForEntity(AZ::EntityId entityId, AzFramework::BehaviorComponentId componentId, const AZStd::string& json)
{
if (!componentId.IsValid())
{
AZ_Error("EntityUtilityComponent", false, "Invalid componentId passed to UpdateComponentForEntity");
return false;
}
AZ::Component* component = FindComponentHelper(entityId, AZ::TypeId::CreateNull(), componentId);
if (!component)
{
return false;
}
using namespace AZ::JsonSerializationResult;
AZ::JsonDeserializerSettings settings = AZ::JsonDeserializerSettings{};
settings.m_reporting = []([[maybe_unused]] AZStd::string_view message, ResultCode result, AZStd::string_view) -> auto
{
if (result.GetProcessing() == Processing::Halted)
{
AZ_Error("EntityUtilityComponent", false, "JSON %s\n", message.data());
}
else if (result.GetOutcome() > Outcomes::PartialDefaults)
{
AZ_Warning("EntityUtilityComponent", false, "JSON %s\n", message.data());
}
return result;
};
rapidjson::Document doc;
doc.Parse<rapidjson::kParseCommentsFlag>(json.data(), json.size());
ResultCode resultCode = AZ::JsonSerialization::Load(*component, doc, settings);
return resultCode.GetProcessing() != Processing::Halted;
}
AZStd::string EntityUtilityComponent::GetComponentDefaultJson(const AZStd::string& typeName)
{
AZ::TypeId typeId = GetComponentTypeIdFromName(typeName);
if (typeId.IsNull())
{
// GetComponentTypeIdFromName already does error handling
return "";
}
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
const AZ::SerializeContext::ClassData* classData = serializeContext->FindClassData(typeId);
if (!classData)
{
AZ_Error("EntityUtilityComponent", false, "Failed to find ClassData for typeId %s (%s)", typeId.ToString<AZStd::string>().c_str(), typeName.c_str());
return "";
}
void* component = classData->m_factory->Create("Component");
rapidjson::Document document;
AZ::JsonSerializerSettings settings;
settings.m_keepDefaults = true;
auto resultCode = AZ::JsonSerialization::Store(document, document.GetAllocator(), component, nullptr, typeId, settings);
// Clean up the allocated component ASAP, we don't need it anymore
classData->m_factory->Destroy(component);
if (resultCode.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
{
AZ_Error("EntityUtilityComponent", false, "Failed to serialize component to json (%s): %s",
typeName.c_str(), resultCode.ToString(typeName).c_str())
return "";
}
AZStd::string jsonString;
AZ::Outcome<void, AZStd::string> outcome = AZ::JsonSerializationUtils::WriteJsonString(document, jsonString);
if (!outcome.IsSuccess())
{
AZ_Error("EntityUtilityComponent", false, "Failed to write component json to string: %s", outcome.GetError().c_str());
return "";
}
return jsonString;
}
AZStd::vector<ComponentDetails> EntityUtilityComponent::FindMatchingComponents(const AZStd::string& searchTerm)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (m_typeInfo.empty())
{
serializeContext->EnumerateDerived<AZ::Component>(
[this, serializeContext](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid& /*typeId*/)
{
auto& typeInfo = m_typeInfo.emplace_back(classData->m_typeId, classData->m_name, AZStd::vector<AZStd::string>{});
serializeContext->EnumerateBase(
[&typeInfo](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid&)
{
if (classData)
{
AZStd::get<2>(typeInfo).emplace_back(classData->m_name);
}
return true;
},
classData->m_typeId);
return true;
});
}
AZStd::vector<ComponentDetails> matches;
for (const auto& [typeId, typeName, baseClasses] : m_typeInfo)
{
if (AZStd::wildcard_match(searchTerm, typeName))
{
ComponentDetails details;
details.m_typeInfo = AZStd::string::format("%s %s", typeId.ToString<AZStd::string>().c_str(), typeName.c_str());
details.m_baseClasses = baseClasses;
matches.emplace_back(AZStd::move(details));
}
}
return matches;
}
void EntityUtilityComponent::ResetEntityContext()
{
for (AZ::EntityId entityId : m_createdEntities)
{
m_entityContext->DestroyEntityById(entityId);
}
m_createdEntities.clear();
m_entityContext->ResetContext();
}
void EntityUtilityComponent::Reflect(AZ::ReflectContext* context)
{
ComponentDetails::Reflect(context);
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EntityUtilityComponent, AZ::Component>();
}
if (auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->ConstantProperty("InvalidComponentId", BehaviorConstant(AZ::InvalidComponentId))
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Entity")
->Attribute(AZ::Script::Attributes::Module, "entity");
behaviorContext->EBus<EntityUtilityBus>("EntityUtilityBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::Category, "Entity")
->Attribute(AZ::Script::Attributes::Module, "entity")
->Event("CreateEditorReadyEntity", &EntityUtilityBus::Events::CreateEditorReadyEntity)
->Event("GetOrAddComponentByTypeName", &EntityUtilityBus::Events::GetOrAddComponentByTypeName)
->Event("UpdateComponentForEntity", &EntityUtilityBus::Events::UpdateComponentForEntity)
->Event("FindMatchingComponents", &EntityUtilityBus::Events::FindMatchingComponents)
->Event("GetComponentDefaultJson", &EntityUtilityBus::Events::GetComponentDefaultJson)
;
}
}
void EntityUtilityComponent::Activate()
{
m_entityContext = AZStd::make_unique<AzFramework::EntityContext>(UtilityEntityContextId);
m_entityContext->InitContext();
EntityUtilityBus::Handler::BusConnect();
}
void EntityUtilityComponent::Deactivate()
{
EntityUtilityBus::Handler::BusDisconnect();
m_entityContext = nullptr;
}
}
@@ -0,0 +1,90 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/ToolsComponents/EditorDisabledCompositionBus.h>
#include <AzToolsFramework/ToolsComponents/EditorPendingCompositionBus.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzFramework/Entity/BehaviorEntity.h>
namespace AzToolsFramework
{
struct ComponentDetails
{
AZ_TYPE_INFO(AzToolsFramework::ComponentDetails, "{107D8379-4AD4-4547-BEE1-184B120F23E9}");
static void Reflect(AZ::ReflectContext* context);
AZStd::string m_typeInfo;
AZStd::vector<AZStd::string> m_baseClasses;
};
// This ebus is intended to provide behavior-context friendly APIs to create and manage entities
struct EntityUtilityTraits : AZ::EBusTraits
{
AZ_RTTI(AzToolsFramework::EntityUtilityTraits, "{A6305CAE-C825-43F9-A44D-E503910912AF}");
virtual ~EntityUtilityTraits() = default;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
// Creates an entity with the default editor components attached and initializes the entity
virtual AZ::EntityId CreateEditorReadyEntity(const AZStd::string& entityName) = 0;
virtual AzFramework::BehaviorComponentId GetOrAddComponentByTypeName(AZ::EntityId entity, const AZStd::string& typeName) = 0;
virtual bool UpdateComponentForEntity(AZ::EntityId entity, AzFramework::BehaviorComponentId component, const AZStd::string& json) = 0;
// Gets a JSON string containing describing the default serialization state of the specified component
virtual AZStd::string GetComponentDefaultJson(const AZStd::string& typeName) = 0;
// Returns a list of matching component type names. Supports wildcard search terms
virtual AZStd::vector<ComponentDetails> FindMatchingComponents(const AZStd::string& searchTerm) = 0;
virtual void ResetEntityContext() = 0;
};
using EntityUtilityBus = AZ::EBus<EntityUtilityTraits>;
struct EntityUtilityComponent : AZ::Component
, EntityUtilityBus::Handler
{
inline const static AZ::Uuid UtilityEntityContextId = AZ::Uuid("{9C277B88-E79E-4F8A-BAFF-A4C175BD565F}");
AZ_COMPONENT(EntityUtilityComponent, "{47205907-A0EA-4FFF-A620-04D20C04A379}");
AZ::EntityId CreateEditorReadyEntity(const AZStd::string& entityName) override;
AzFramework::BehaviorComponentId GetOrAddComponentByTypeName(AZ::EntityId entity, const AZStd::string& typeName) override;
bool UpdateComponentForEntity(AZ::EntityId entity, AzFramework::BehaviorComponentId component, const AZStd::string& json) override;
AZStd::string GetComponentDefaultJson(const AZStd::string& typeName) override;
AZStd::vector<ComponentDetails> FindMatchingComponents(const AZStd::string& searchTerm) override;
void ResetEntityContext() override;
static void Reflect(AZ::ReflectContext* context);
protected:
void Activate() override;
void Deactivate() override;
// Our own entity context. This API is intended mostly for use in Asset Builders where there is no editor context
// Additionally, an entity context is needed when using the Behavior Entity class
AZStd::unique_ptr<AzFramework::EntityContext> m_entityContext;
// TypeId, TypeName, Vector<BaseClassName>
AZStd::vector<AZStd::tuple<AZ::TypeId, AZStd::string, AZStd::vector<AZStd::string>>> m_typeInfo;
// Keep track of the entities we create so they can be reset
AZStd::vector<AZ::EntityId> m_createdEntities;
};
}; // namespace AzToolsFramework
@@ -10,6 +10,7 @@
#include <AzCore/Component/EntityId.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Entity/EntityContextBus.h>
@@ -224,6 +224,7 @@ namespace AzToolsFramework
return false;
}
AZ::Data::SerializedAssetTracker* assetTracker = settings.m_metadata.Find<AZ::Data::SerializedAssetTracker>();
referencedAssets = AZStd::move(assetTracker->GetTrackedAssets());
@@ -245,6 +246,30 @@ namespace AzToolsFramework
entityIdMapper.SetEntityIdGenerationApproach(InstanceEntityIdMapper::EntityIdGenerationApproach::Random);
}
// some assets may come in from the JSON serialzier with no AssetID, but have an asset hint
// this attempts to fix up the assets using the assetHint field
auto fixUpInvalidAssets = [](AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
if (!asset.GetId().IsValid() && !asset.GetHint().empty())
{
AZ::Data::AssetId assetId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
assetId,
&AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
asset.GetHint().c_str(),
AZ::Data::s_invalidAssetType,
false);
if (assetId.IsValid())
{
asset.Create(assetId, true);
}
}
};
auto tracker = AZ::Data::SerializedAssetTracker{};
tracker.SetAssetFixUp(fixUpInvalidAssets);
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
@@ -252,16 +277,17 @@ namespace AzToolsFramework
settings.m_metadata.Add(static_cast<AZ::JsonEntityIdSerializer::JsonEntityIdMapper*>(&entityIdMapper));
settings.m_metadata.Add(&entityIdMapper);
settings.m_metadata.Create<InstanceEntityScrubber>(newlyAddedEntities);
settings.m_metadata.Add(tracker);
AZStd::string scratchBuffer;
auto issueReportingCallback = [&scratchBuffer](
AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result,
AZStd::string_view path) -> AZ::JsonSerializationResult::ResultCode
AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result,
AZStd::string_view path) -> AZ::JsonSerializationResult::ResultCode
{
return Internal::JsonIssueReporter(scratchBuffer, message, result, path);
};
settings.m_reporting = AZStd::move(issueReportingCallback);
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(instance, prefabDom, settings);
AZ::Data::AssetManager::Instance().ResumeAssetRelease();
@@ -50,17 +50,19 @@ namespace AzToolsFramework
auto settingsRegistry = AZ::SettingsRegistry::Get();
AZ_Assert(settingsRegistry, "Settings registry is not set");
[[maybe_unused]] bool result =
settingsRegistry->Get(m_projectPathWithOsSeparator.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
AZ_Warning("Prefab", result, "Couldn't retrieve project root path");
m_projectPathWithSlashSeparator = AZ::IO::Path(m_projectPathWithOsSeparator.Native(), '/').MakePreferred();
AZ::Interface<PrefabLoaderInterface>::Register(this);
m_scriptingPrefabLoader.Connect(this);
}
void PrefabLoader::UnregisterPrefabLoaderInterface()
{
m_scriptingPrefabLoader.Disconnect();
AZ::Interface<PrefabLoaderInterface>::Unregister(this);
}
@@ -568,7 +570,7 @@ namespace AzToolsFramework
(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();
@@ -596,26 +598,38 @@ namespace AzToolsFramework
{
// The asset system provided us with a valid root folder and relative path, so return it.
fullPath = AZ::IO::Path(rootFolder) / assetInfo.m_relativePath;
return fullPath;
}
else
{
// If for some reason the Asset system couldn't provide a relative path, provide some fallback logic.
// Check to see if the AssetProcessor is ready. If it *is* and we didn't get a path, print an error then follow
// the fallback logic. If it's *not* ready, we're probably either extremely early in a tool startup flow or inside
// a unit test, so just execute the fallback logic without an error.
[[maybe_unused]] bool assetProcessorReady = false;
AzFramework::AssetSystemRequestBus::BroadcastResult(
assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady);
AZ_Error(
"Prefab", !assetProcessorReady, "Full source path for '%.*s' could not be determined. Using fallback logic.",
AZ_STRING_ARG(path.Native()));
// If a relative path was passed in, make it relative to the project root.
fullPath = AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator);
// attempt to find the absolute from the Cache folder
AZStd::string assetRootFolder;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
settingsRegistry->Get(assetRootFolder, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
}
fullPath = AZ::IO::Path(assetRootFolder) / path;
if (fullPath.IsAbsolute() && AZ::IO::SystemFile::Exists(fullPath.c_str()))
{
return fullPath;
}
}
// If for some reason the Asset system couldn't provide a relative path, provide some fallback logic.
// Check to see if the AssetProcessor is ready. If it *is* and we didn't get a path, print an error then follow
// the fallback logic. If it's *not* ready, we're probably either extremely early in a tool startup flow or inside
// a unit test, so just execute the fallback logic without an error.
[[maybe_unused]] bool assetProcessorReady = false;
AzFramework::AssetSystemRequestBus::BroadcastResult(
assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady);
AZ_Error(
"Prefab", !assetProcessorReady, "Full source path for '%.*s' could not be determined. Using fallback logic.",
AZ_STRING_ARG(path.Native()));
// If a relative path was passed in, make it relative to the project root.
fullPath = AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator);
return fullPath;
}
@@ -15,6 +15,7 @@
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/string/string.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <Prefab/ScriptingPrefabLoader.h>
namespace AZ
{
@@ -114,6 +115,7 @@ namespace AzToolsFramework
void SetSaveAllPrefabsPreference(SaveAllPrefabsPreference saveAllPrefabsPreference) override;
private:
/**
* Copies the template dom provided and manipulates it into the proper format to be saved to disk.
* @param templateRef The template whose dom we want to transform into the proper format to be saved to disk.
@@ -177,6 +179,7 @@ namespace AzToolsFramework
AZStd::optional<AZStd::pair<PrefabDom, AZ::IO::Path>> StoreTemplateIntoFileFormat(TemplateId templateId);
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
ScriptingPrefabLoader m_scriptingPrefabLoader;
AZ::IO::Path m_projectPathWithOsSeparator;
AZ::IO::Path m_projectPathWithSlashSeparator;
};
@@ -99,7 +99,6 @@ namespace AzToolsFramework
// Generates a new path
static AZ::IO::Path GeneratePath();
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -0,0 +1,45 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
#include <AzCore/EBus/EBus.h>
namespace AzToolsFramework
{
namespace Prefab
{
// Ebus for script-friendly APIs for the prefab loader
struct PrefabLoaderScriptingTraits : AZ::EBusTraits
{
AZ_TYPE_INFO(PrefabLoaderScriptingTraits, "{C344B7D8-8299-48C9-8450-26E1332EA011}");
virtual ~PrefabLoaderScriptingTraits() = default;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
/**
* 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
* @return Will contain the serialized template json on success
*/
virtual AZ::Outcome<AZStd::string, void> SaveTemplateToString(TemplateId templateId) = 0;
};
using PrefabLoaderScriptingBus = AZ::EBus<PrefabLoaderScriptingTraits>;
} // namespace Prefab
} // namespace AzToolsFramework
@@ -10,6 +10,7 @@
#include <AzCore/Component/Entity.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
@@ -36,12 +37,14 @@ namespace AzToolsFramework
m_instanceToTemplatePropagator.RegisterInstanceToTemplateInterface();
m_prefabPublicHandler.RegisterPrefabPublicHandlerInterface();
m_prefabPublicRequestHandler.Connect();
m_prefabSystemScriptingHandler.Connect(this);
AZ::SystemTickBus::Handler::BusConnect();
}
void PrefabSystemComponent::Deactivate()
{
AZ::SystemTickBus::Handler::BusDisconnect();
m_prefabSystemScriptingHandler.Disconnect();
m_prefabPublicRequestHandler.Disconnect();
m_prefabPublicHandler.UnregisterPrefabPublicHandlerInterface();
m_instanceToTemplatePropagator.UnregisterInstanceToTemplateInterface();
@@ -58,13 +61,24 @@ namespace AzToolsFramework
AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover::Reflect(context);
PrefabPublicRequestHandler::Reflect(context);
PrefabLoader::Reflect(context);
PrefabSystemScriptingHandler::Reflect(context);
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<PrefabSystemComponent, AZ::Component>()->Version(1);
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<PrefabLoaderScriptingBus>("PrefabLoaderScriptingBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "prefab")
->Attribute(AZ::Script::Attributes::Category, "Prefab")
->Event("SaveTemplateToString", &PrefabLoaderScriptingBus::Events::SaveTemplateToString);
;
}
AZ::JsonRegistrationContext* jsonRegistration = azrtti_cast<AZ::JsonRegistrationContext*>(context);
if (jsonRegistration)
{
@@ -145,7 +159,7 @@ namespace AzToolsFramework
newInstance->SetTemplateId(newTemplateId);
}
}
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude)
{
UpdatePrefabInstances(templateId, immediate, instanceToExclude);
@@ -27,6 +27,7 @@
#include <AzToolsFramework/Prefab/PrefabPublicRequestHandler.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
#include <Prefab/PrefabSystemScriptingHandler.h>
namespace AZ
{
@@ -219,7 +220,7 @@ namespace AzToolsFramework
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume,
AZ::IO::PathView filePath, AZStd::unique_ptr<AZ::Entity> containerEntity = nullptr,
InstanceOptionalReference parent = AZStd::nullopt, bool shouldCreateLinks = true) override;
PrefabDom& FindTemplateDom(TemplateId templateId) override;
/**
@@ -244,7 +245,7 @@ namespace AzToolsFramework
private:
AZ_DISABLE_COPY_MOVE(PrefabSystemComponent);
/**
* Builds a new Prefab Template out of entities and instances and returns the first instance comprised of
* these entities and instances.
@@ -412,6 +413,8 @@ namespace AzToolsFramework
// Handler of the public Prefab requests.
PrefabPublicRequestHandler m_prefabPublicRequestHandler;
PrefabSystemScriptingHandler m_prefabSystemScriptingHandler;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -78,8 +78,7 @@ namespace AzToolsFramework
AZStd::unique_ptr<AZ::Entity> containerEntity = nullptr, InstanceOptionalReference parent = AZStd::nullopt,
bool shouldCreateLinks = true) = 0;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -0,0 +1,38 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Link/Link.h>
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/EBus/EBus.h>
namespace AzToolsFramework
{
namespace Prefab
{
// Bus that exposes a script-friendly interface to the PrefabSystemComponent
struct PrefabSystemScriptingEbusTraits : AZ::EBusTraits
{
using MutexType = AZ::NullMutex;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual TemplateId CreatePrefabTemplate(
const AZStd::vector<AZ::EntityId>& entityIds, const AZStd::string& filePath) = 0;
};
using PrefabSystemScriptingBus = AZ::EBus<PrefabSystemScriptingEbusTraits>;
} // namespace Prefab
} // namespace AzToolsFramework
@@ -0,0 +1,75 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <Prefab/PrefabSystemComponentInterface.h>
#include <Prefab/PrefabSystemScriptingHandler.h>
#include <AzCore/Component/Entity.h>
namespace AzToolsFramework::Prefab
{
void PrefabSystemScriptingHandler::Reflect(AZ::ReflectContext* context)
{
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->ConstantProperty("InvalidTemplateId", BehaviorConstant(InvalidTemplateId))
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "prefab")
->Attribute(AZ::Script::Attributes::Category, "Prefab");
behaviorContext->EBus<PrefabSystemScriptingBus>("PrefabSystemScriptingBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "prefab")
->Attribute(AZ::Script::Attributes::Category, "Prefab")
->Event("CreatePrefab", &PrefabSystemScriptingBus::Events::CreatePrefabTemplate);
}
}
void PrefabSystemScriptingHandler::Connect(PrefabSystemComponentInterface* prefabSystemComponentInterface)
{
AZ_Assert(prefabSystemComponentInterface != nullptr, "prefabSystemComponentInterface must not be null");
m_prefabSystemComponentInterface = prefabSystemComponentInterface;
PrefabSystemScriptingBus::Handler::BusConnect();
}
void PrefabSystemScriptingHandler::Disconnect()
{
PrefabSystemScriptingBus::Handler::BusDisconnect();
}
TemplateId PrefabSystemScriptingHandler::CreatePrefabTemplate(const AZStd::vector<AZ::EntityId>& entityIds, const AZStd::string& filePath)
{
AZStd::vector<AZ::Entity*> entities;
for (const auto& entityId : entityIds)
{
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
AZ_Warning(
"PrefabSystemComponent", entity, "EntityId %s was not found and will not be added to the prefab",
entityId.ToString().c_str());
if (entity)
{
entities.push_back(entity);
}
}
auto prefab = m_prefabSystemComponentInterface->CreatePrefab(entities, {}, AZ::IO::PathView(AZStd::string_view(filePath)));
if (!prefab)
{
AZ_Error("PrefabSystemComponenent", false, "Failed to create prefab %s", filePath.c_str());
return InvalidTemplateId;
}
return prefab->GetTemplateId();
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Prefab/PrefabSystemScriptingBus.h>
namespace AzToolsFramework
{
namespace Prefab
{
class PrefabSystemScriptingHandler
: PrefabSystemScriptingBus::Handler
{
public:
static void Reflect(AZ::ReflectContext* context);
PrefabSystemScriptingHandler() = default;
void Connect(PrefabSystemComponentInterface* prefabSystemComponentInterface);
void Disconnect();
private:
AZ_DISABLE_COPY(PrefabSystemScriptingHandler);
//////////////////////////////////////////////////////////////////////////
// PrefabSystemScriptingBus implementation
TemplateId CreatePrefabTemplate(const AZStd::vector<AZ::EntityId>& entityIds, const AZStd::string& filePath) override;
//////////////////////////////////////////////////////////////////////////
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -0,0 +1,144 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Prefab/Procedural/ProceduralPrefabAsset.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzFramework/FileFunc/FileFunc.h>
namespace AZ::Prefab
{
static constexpr const char s_useProceduralPrefabsKey[] = "/O3DE/Preferences/Prefabs/UseProceduralPrefabs";
// ProceduralPrefabAsset
ProceduralPrefabAsset::ProceduralPrefabAsset(const AZ::Data::AssetId& assetId)
: AZ::Data::AssetData(assetId)
, m_templateId(AzToolsFramework::Prefab::InvalidTemplateId)
{
}
void ProceduralPrefabAsset::Reflect(AZ::ReflectContext* context)
{
PrefabDomData::Reflect(context);
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<ProceduralPrefabAsset, AZ::Data::AssetData>()
->Version(1)
->Field("Template Name", &ProceduralPrefabAsset::m_templateName)
->Field("Template ID", &ProceduralPrefabAsset::m_templateId);
}
}
const AZStd::string& ProceduralPrefabAsset::GetTemplateName() const
{
return m_templateName;
}
void ProceduralPrefabAsset::SetTemplateName(AZStd::string templateName)
{
m_templateName = AZStd::move(templateName);
}
AzToolsFramework::Prefab::TemplateId ProceduralPrefabAsset::GetTemplateId() const
{
return m_templateId;
}
void ProceduralPrefabAsset::SetTemplateId(AzToolsFramework::Prefab::TemplateId templateId)
{
m_templateId = templateId;
}
bool ProceduralPrefabAsset::UseProceduralPrefabs()
{
bool useProceduralPrefabs = false;
bool result = AZ::SettingsRegistry::Get()->GetObject(useProceduralPrefabs, s_useProceduralPrefabsKey);
return result && useProceduralPrefabs;
}
// PrefabDomData
void PrefabDomData::Reflect(AZ::ReflectContext* context)
{
if (auto* jsonContext = azrtti_cast<AZ::JsonRegistrationContext*>(context))
{
jsonContext->Serializer<PrefabDomDataJsonSerializer>()->HandlesType<PrefabDomData>();
}
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<PrefabDomData>()
->Version(1);
}
}
void PrefabDomData::CopyValue(const rapidjson::Value& inputValue)
{
m_prefabDom.CopyFrom(inputValue, m_prefabDom.GetAllocator());
}
const AzToolsFramework::Prefab::PrefabDom& PrefabDomData::GetValue() const
{
return m_prefabDom;
}
// PrefabDomDataJsonSerializer
AZ::JsonSerializationResult::Result PrefabDomDataJsonSerializer::Load(
void* outputValue,
[[maybe_unused]] const AZ::Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
AZ::JsonDeserializerContext& context)
{
AZ_Assert(outputValueTypeId == azrtti_typeid<PrefabDomData>(),
"PrefabDomDataJsonSerializer Load against output typeID that was not PrefabDomData");
AZ_Assert(outputValue, "PrefabDomDataJsonSerializer Load against null output");
namespace JSR = AZ::JsonSerializationResult;
JSR::ResultCode result(JSR::Tasks::ReadField);
if (inputValue.IsObject() == false)
{
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Missing, "Missing object"));
return context.Report(result, "Prefab should be an object.");
}
if (inputValue.MemberCount() < 1)
{
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Missing, "Missing members"));
return context.Report(result, "Prefab should have multiple members.");
}
auto* outputVariable = reinterpret_cast<PrefabDomData*>(outputValue);
outputVariable->CopyValue(inputValue);
return context.Report(result, "Loaded procedural prefab");
}
AZ::JsonSerializationResult::Result PrefabDomDataJsonSerializer::Store(
rapidjson::Value& outputValue,
const void* inputValue,
[[maybe_unused]] const void* defaultValue,
[[maybe_unused]] const AZ::Uuid& valueTypeId,
AZ::JsonSerializerContext& context)
{
AZ_Assert(inputValue, "Input value for PrefabDomDataJsonSerializer can't be null.");
AZ_Assert(azrtti_typeid<PrefabDomData>() == valueTypeId,
"Unable to Serialize because the provided type is not PrefabGroup::PrefabDomData.");
const PrefabDomData* prefabDomData = reinterpret_cast<const PrefabDomData*>(inputValue);
namespace JSR = AZ::JsonSerializationResult;
JSR::ResultCode result(JSR::Tasks::WriteValue);
outputValue.SetObject();
outputValue.CopyFrom(prefabDomData->GetValue(), context.GetJsonAllocator());
return context.Report(result, "Stored procedural prefab");
}
}
@@ -0,0 +1,90 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
namespace AZ::Prefab
{
//! A wrapper around the JSON DOM type so that the assets can read in and write out
//! JSON directly since Prefabs are JSON serialized entity-component data
class PrefabDomData final
{
public:
AZ_RTTI(PrefabDomData, "{C73A3360-D772-4D41-9118-A039BF9340C1}");
AZ_CLASS_ALLOCATOR(PrefabDomData, AZ::SystemAllocator, 0);
PrefabDomData() = default;
~PrefabDomData() = default;
static void Reflect(AZ::ReflectContext* context);
void CopyValue(const rapidjson::Value& inputValue);
const AzToolsFramework::Prefab::PrefabDom& GetValue() const;
private:
AzToolsFramework::Prefab::PrefabDom m_prefabDom;
};
//! Registered to help read/write JSON for the PrefabDomData::m_prefabDom
class PrefabDomDataJsonSerializer final
: public AZ::BaseJsonSerializer
{
public:
AZ_RTTI(PrefabDomDataJsonSerializer, "{9FC48652-A00B-4EFA-8FD9-345A8E625439}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR(PrefabDomDataJsonSerializer, AZ::SystemAllocator, 0);
~PrefabDomDataJsonSerializer() override = default;
AZ::JsonSerializationResult::Result Load(
void* outputValue,
const AZ::Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
AZ::JsonDeserializerContext& context) override;
AZ::JsonSerializationResult::Result Store(
rapidjson::Value& outputValue,
const void* inputValue,
const void* defaultValue,
const AZ::Uuid& valueTypeId,
AZ::JsonSerializerContext& context) override;
};
//! An asset type to register templates into the Prefab system so that they
//! can instantiate like Authored Prefabs
class ProceduralPrefabAsset
: public AZ::Data::AssetData
{
public:
AZ_CLASS_ALLOCATOR(ProceduralPrefabAsset, AZ::SystemAllocator, 0);
AZ_RTTI(ProceduralPrefabAsset, "{9B7C8459-471E-4EAD-A363-7990CC4065A9}", AZ::Data::AssetData);
static bool UseProceduralPrefabs();
ProceduralPrefabAsset(const AZ::Data::AssetId& assetId = AZ::Data::AssetId());
~ProceduralPrefabAsset() override = default;
ProceduralPrefabAsset(const ProceduralPrefabAsset& rhs) = delete;
ProceduralPrefabAsset& operator=(const ProceduralPrefabAsset& rhs) = delete;
const AZStd::string& GetTemplateName() const;
void SetTemplateName(AZStd::string templateName);
AzToolsFramework::Prefab::TemplateId GetTemplateId() const;
void SetTemplateId(AzToolsFramework::Prefab::TemplateId templateId);
static void Reflect(AZ::ReflectContext* context);
private:
AZStd::string m_templateName;
AzToolsFramework::Prefab::TemplateId m_templateId;
};
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Prefab/ScriptingPrefabLoader.h>
namespace AzToolsFramework::Prefab
{
void ScriptingPrefabLoader::Connect(PrefabLoaderInterface* prefabLoaderInterface)
{
AZ_Assert(prefabLoaderInterface, "prefabLoaderInterface must not be null");
m_prefabLoaderInterface = prefabLoaderInterface;
PrefabLoaderScriptingBus::Handler::BusConnect();
}
void ScriptingPrefabLoader::Disconnect()
{
PrefabLoaderScriptingBus::Handler::BusDisconnect();
}
AZ::Outcome<AZStd::string, void> ScriptingPrefabLoader::SaveTemplateToString(TemplateId templateId)
{
AZStd::string json;
if (m_prefabLoaderInterface->SaveTemplateToString(templateId, json))
{
return AZ::Success(json);
}
return AZ::Failure();
}
} // namespace AzToolsFramework::Prefab
@@ -0,0 +1,42 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Prefab/PrefabLoaderInterface.h>
#include <Prefab/PrefabLoaderScriptingBus.h>
namespace AzToolsFramework
{
namespace Prefab
{
/**
* The Scripting Prefab Loader handles scripting-friendly API requests for the prefab loader
*/
class ScriptingPrefabLoader
: private PrefabLoaderScriptingBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(ScriptingPrefabLoader, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptingPrefabLoader, "{ABC3C989-4D4F-41E7-B25B-B0FEF97177E6}");
void Connect(PrefabLoaderInterface* prefabLoaderInterface);
void Disconnect();
private:
//////////////////////////////////////////////////////////////////////////
// PrefabLoaderRequestBus implementation
AZ::Outcome<AZStd::string, void> SaveTemplateToString(TemplateId templateId) override;
//////////////////////////////////////////////////////////////////////////
PrefabLoaderInterface* m_prefabLoaderInterface = nullptr;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -1025,7 +1025,7 @@ namespace AzToolsFramework
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/LuaScript.svg")
->Attribute(AZ::Edit::Attributes::PrimaryAssetType, AZ::AzTypeInfo<AZ::ScriptAsset>::Uuid())
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Script.png")
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/lua-script/")
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/scripting/lua-script/")
->DataElement("AssetRef", &ScriptEditorComponent::m_scriptAsset, "Script", "Which script to use")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &ScriptEditorComponent::ScriptHasChanged)
->Attribute("BrowseIcon", ":/stylesheet/img/UI20/browse-edit-select-files.svg")
@@ -13,6 +13,7 @@
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Asset/AssetSystemBus.h>
@@ -25,6 +26,9 @@
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
@@ -218,6 +222,16 @@ namespace AzToolsFramework
instantiateAction, &QAction::triggered, instantiateAction, [] { ContextMenu_InstantiatePrefab(); });
}
// Instantiate Procedural Prefab
if (AZ::Prefab::ProceduralPrefabAsset::UseProceduralPrefabs())
{
QAction* action = menu->addAction(QObject::tr("Instantiate Procedural Prefab..."));
action->setToolTip(QObject::tr("Instantiates a procedural prefab file in a prefab."));
QObject::connect(
action, &QAction::triggered, action, [] { ContextMenu_InstantiateProceduralPrefab(); });
}
menu->addSeparator();
bool itemWasShown = false;
@@ -435,6 +449,38 @@ namespace AzToolsFramework
}
}
void PrefabIntegrationManager::ContextMenu_InstantiateProceduralPrefab()
{
AZStd::string prefabAssetPath;
bool hasUserForProceduralPrefabAsset = QueryUserForProceduralPrefabAsset(prefabAssetPath);
if (hasUserForProceduralPrefabAsset)
{
AZ::EntityId parentId;
AZ::Vector3 position = AZ::Vector3::CreateZero();
EntityIdList selectedEntities;
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
if (selectedEntities.size() == 1)
{
parentId = selectedEntities.front();
AZ::TransformBus::EventResult(position, parentId, &AZ::TransformInterface::GetWorldTranslation);
}
else
{
// otherwise return since it needs to be inside an authored prefab
return;
}
// Instantiating from context menu always puts the instance at the root level
auto createPrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(prefabAssetPath, parentId, position);
if (!createPrefabOutcome.IsSuccess())
{
WarnUserOfError("Prefab Instantiation Error", createPrefabOutcome.GetError());
}
}
}
void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity)
{
s_prefabFocusInterface->FocusOnOwningPrefab(containerEntity);
@@ -690,6 +736,33 @@ namespace AzToolsFramework
return true;
}
bool PrefabIntegrationManager::QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath)
{
using namespace AzToolsFramework;
auto selection = AssetBrowser::AssetSelectionModel::AssetTypeSelection(azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>());
EditorRequests::Bus::Broadcast(&AzToolsFramework::EditorRequests::BrowseForAssets, selection);
if (!selection.IsValid())
{
return false;
}
auto product = azrtti_cast<const ProductAssetBrowserEntry*>(selection.GetResult());
if (product == nullptr)
{
return false;
}
outPrefabAssetPath = product->GetRelativePath();
auto asset = AZ::Data::AssetManager::Instance().GetAsset(
product->GetAssetId(),
azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>(),
AZ::Data::AssetLoadBehavior::Default);
return asset.BlockUntilLoadComplete() != AZ::Data::AssetData::AssetStatus::Error;
}
void PrefabIntegrationManager::WarnUserOfError(AZStd::string_view title, AZStd::string_view message)
{
QWidget* activeWindow = QApplication::activeWindow();
@@ -91,6 +91,7 @@ namespace AzToolsFramework
// Context menu item handlers
static void ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities);
static void ContextMenu_InstantiatePrefab();
static void ContextMenu_InstantiateProceduralPrefab();
static void ContextMenu_EditPrefab(AZ::EntityId containerEntity);
static void ContextMenu_SavePrefab(AZ::EntityId containerEntity);
static void ContextMenu_DeleteSelected();
@@ -101,6 +102,7 @@ namespace AzToolsFramework
const AZStd::string& suggestedName, const char* initialTargetDirectory, AZ::u32 prefabUserSettingsId, QWidget* activeWindow,
AZStd::string& outPrefabName, AZStd::string& outPrefabFilePath);
static bool QueryUserForPrefabFilePath(AZStd::string& outPrefabFilePath);
static bool QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath);
static void WarnUserOfError(AZStd::string_view title, AZStd::string_view message);
// Path and filename generation
@@ -103,10 +103,6 @@ namespace AzToolsFramework
static const char* const ResetEntityTransformDesc = "Reset transform based on manipulator mode";
static const char* const ResetManipulatorTitle = "Reset Manipulator";
static const char* const ResetManipulatorDesc = "Reset the manipulator to recenter it on the selected entity";
static const char* const ResetTransformLocalTitle = "Reset Transform (Local)";
static const char* const ResetTransformLocalDesc = "Reset transform to local space";
static const char* const ResetTransformWorldTitle = "Reset Transform (World)";
static const char* const ResetTransformWorldDesc = "Reset transform to world space";
static const char* const EntityBoxSelectUndoRedoDesc = "Box Select Entities";
static const char* const EntityDeselectUndoRedoDesc = "Deselect Entity";
@@ -2424,45 +2420,9 @@ namespace AzToolsFramework
AddAction(
m_actions, { QKeySequence(Qt::CTRL + Qt::Key_R) }, EditResetManipulator, ResetManipulatorTitle, ResetManipulatorDesc,
AZStd::bind(AZStd::mem_fn(&EditorTransformComponentSelection::DelegateClearManipulatorOverride), this));
AddAction(
m_actions, { QKeySequence(Qt::ALT + Qt::Key_R) }, EditResetLocal, ResetTransformLocalTitle, ResetTransformLocalDesc,
[this]()
[this]
{
switch (m_mode)
{
case Mode::Rotation:
ResetOrientationForSelectedEntitiesLocal();
break;
case Mode::Scale:
CopyScaleToSelectedEntitiesIndividualWorld(1.0f);
break;
case Mode::Translation:
// do nothing
break;
}
});
AddAction(
m_actions, { QKeySequence(Qt::SHIFT + Qt::Key_R) }, EditResetWorld, ResetTransformWorldTitle, ResetTransformWorldDesc,
[this]()
{
switch (m_mode)
{
case Mode::Rotation:
{
// begin an undo batch so operations inside CopyOrientation... and
// DelegateClear... are grouped into a single undo/redo
ScopedUndoBatch undoBatch{ ResetTransformWorldTitle };
CopyOrientationToSelectedEntitiesIndividual(AZ::Quaternion::CreateIdentity());
ClearManipulatorOrientationOverride();
}
break;
case Mode::Scale:
case Mode::Translation:
break;
}
DelegateClearManipulatorOverride();
});
AddAction(
@@ -31,8 +31,6 @@ namespace AzToolsFramework
constexpr inline AZ::Crc32 EditPivot = AZ_CRC_CE("com.o3de.action.editortransform.editpivot");
constexpr inline AZ::Crc32 EditReset = AZ_CRC_CE("com.o3de.action.editortransform.editreset");
constexpr inline AZ::Crc32 EditResetManipulator = AZ_CRC_CE("com.o3de.action.editortransform.editresetmanipulator");
constexpr inline AZ::Crc32 EditResetLocal = AZ_CRC_CE("com.o3de.action.editortransform.editresetlocal");
constexpr inline AZ::Crc32 EditResetWorld = AZ_CRC_CE("com.o3de.action.editortransform.editresetworld");
constexpr inline AZ::Crc32 ViewportUiVisible = AZ_CRC_CE("com.o3de.action.editortransform.viewportuivisible");
//@}
@@ -154,6 +154,8 @@ set(FILES
Entity/SliceEditorEntityOwnershipService.h
Entity/SliceEditorEntityOwnershipService.cpp
Entity/SliceEditorEntityOwnershipServiceBus.h
Entity/EntityUtilityComponent.h
Entity/EntityUtilityComponent.cpp
Fingerprinting/TypeFingerprinter.h
Fingerprinting/TypeFingerprinter.cpp
FocusMode/FocusModeInterface.h
@@ -648,9 +650,15 @@ set(FILES
Prefab/PrefabLoader.h
Prefab/PrefabLoader.cpp
Prefab/PrefabLoaderInterface.h
Prefab/PrefabLoaderScriptingBus.h
Prefab/ScriptingPrefabLoader.h
Prefab/ScriptingPrefabLoader.cpp
Prefab/PrefabSystemComponent.h
Prefab/PrefabSystemComponent.cpp
Prefab/PrefabSystemComponentInterface.h
Prefab/PrefabSystemScriptingBus.h
Prefab/PrefabSystemScriptingHandler.h
Prefab/PrefabSystemScriptingHandler.cpp
Prefab/Instance/Instance.h
Prefab/Instance/Instance.cpp
Prefab/Instance/InstanceSerializer.h
@@ -673,6 +681,8 @@ set(FILES
Prefab/Instance/TemplateInstanceMapperInterface.h
Prefab/Link/Link.h
Prefab/Link/Link.cpp
Prefab/Procedural/ProceduralPrefabAsset.h
Prefab/Procedural/ProceduralPrefabAsset.cpp
Prefab/PrefabPublicHandler.h
Prefab/PrefabPublicHandler.cpp
Prefab/PrefabPublicInterface.h
@@ -0,0 +1,252 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzFramework/Entity/BehaviorEntity.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <Entity/EntityUtilityComponent.h>
#include <ToolsComponents/TransformComponent.h>
namespace UnitTest
{
// Global variables for communicating between Lua test code and C++
AZ::EntityId g_globalEntityId = AZ::EntityId{};
AZStd::string g_globalString = "";
AzFramework::BehaviorComponentId g_globalComponentId = {};
AZStd::vector<AzToolsFramework::ComponentDetails> g_globalComponentDetails = {};
bool g_globalBool = false;
class EntityUtilityComponentTests
: public ToolsApplicationFixture
{
void InitProperties()
{
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
ASSERT_NE(componentApplicationRequests, nullptr);
auto behaviorContext = componentApplicationRequests->GetBehaviorContext();
ASSERT_NE(behaviorContext, nullptr);
behaviorContext->Property("g_globalEntityId", BehaviorValueProperty(&g_globalEntityId));
behaviorContext->Property("g_globalString", BehaviorValueProperty(&g_globalString));
behaviorContext->Property("g_globalComponentId", BehaviorValueProperty(&g_globalComponentId));
behaviorContext->Property("g_globalBool", BehaviorValueProperty(&g_globalBool));
behaviorContext->Property("g_globalComponentDetails", BehaviorValueProperty(&g_globalComponentDetails));
g_globalEntityId = AZ::EntityId{};
g_globalString = AZStd::string{};
g_globalComponentId = AzFramework::BehaviorComponentId{};
g_globalBool = false;
g_globalComponentDetails = AZStd::vector<AzToolsFramework::ComponentDetails>{};
}
void SetUpEditorFixtureImpl() override
{
InitProperties();
}
void TearDownEditorFixtureImpl() override
{
g_globalString.set_capacity(0); // Free all memory
g_globalComponentDetails.set_capacity(0);
}
};
TEST_F(EntityUtilityComponentTests, CreateEntity)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
g_globalEntityId = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
my_entity = Entity(g_globalEntityId)
g_globalString = my_entity:GetName()
)LUA");
EXPECT_NE(g_globalEntityId, AZ::EntityId{});
EXPECT_STREQ(g_globalString.c_str(), "test");
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(g_globalEntityId);
ASSERT_NE(entity, nullptr);
// Test cleaning up, make sure the entity is destroyed
AzToolsFramework::EntityUtilityBus::Broadcast(&AzToolsFramework::EntityUtilityBus::Events::ResetEntityContext);
entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(g_globalEntityId);
ASSERT_EQ(entity, nullptr);
}
TEST_F(EntityUtilityComponentTests, CreateEntityEmptyName)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
g_globalEntityId = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("")
)LUA");
EXPECT_NE(g_globalEntityId, AZ::EntityId{});
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(g_globalEntityId);
ASSERT_NE(entity, nullptr);
}
TEST_F(EntityUtilityComponentTests, FindComponent)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0 TransformComponent")
)LUA");
EXPECT_TRUE(g_globalComponentId.IsValid());
}
TEST_F(EntityUtilityComponentTests, InvalidComponentName)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
AZ_TEST_START_TRACE_SUPPRESSION;
sc.Execute(R"LUA(
ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "ThisIsNotAComponent-Error")
)LUA");
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
EXPECT_FALSE(g_globalComponentId.IsValid());
}
TEST_F(EntityUtilityComponentTests, InvalidComponentId)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
AZ_TEST_START_TRACE_SUPPRESSION;
sc.Execute(R"LUA(
ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "{1234-hello-world-this-is-not-an-id}")
)LUA");
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // Should get 1 error stating the type id is not valid
EXPECT_FALSE(g_globalComponentId.IsValid());
}
TEST_F(EntityUtilityComponentTests, CreateComponent)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "ScriptEditorComponent")
)LUA");
EXPECT_TRUE(g_globalComponentId.IsValid());
}
TEST_F(EntityUtilityComponentTests, UpdateComponent)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
g_globalEntityId = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
comp_id = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(g_globalEntityId, "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent")
json_update = [[
{
"Transform Data": { "Rotate": [0.0, 0.1, 180.0] }
}
]]
g_globalBool = EntityUtilityBus.Broadcast.UpdateComponentForEntity(g_globalEntityId, comp_id, json_update);
)LUA");
EXPECT_TRUE(g_globalBool);
EXPECT_NE(g_globalEntityId, AZ::EntityId(AZ::EntityId::InvalidEntityId));
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(g_globalEntityId);
auto* transformComponent = entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
ASSERT_NE(transformComponent, nullptr);
AZ::Vector3 localRotation = transformComponent->GetLocalRotationQuaternion().GetEulerDegrees();
EXPECT_EQ(localRotation, AZ::Vector3(.0f, 0.1f, 180.0f));
}
TEST_F(EntityUtilityComponentTests, GetComponentJson)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
g_globalString = EntityUtilityBus.Broadcast.GetComponentDefaultJson("ScriptEditorComponent")
)LUA");
EXPECT_STRNE(g_globalString.c_str(), "");
}
TEST_F(EntityUtilityComponentTests, GetComponentJsonDoesNotExist)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
AZ_TEST_START_TRACE_SUPPRESSION;
sc.Execute(R"LUA(
g_globalString = EntityUtilityBus.Broadcast.GetComponentDefaultJson("404")
)LUA");
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // 1 error: Failed to find component id for type name 404
EXPECT_STREQ(g_globalString.c_str(), "");
}
TEST_F(EntityUtilityComponentTests, SearchComponents)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
g_globalComponentDetails = EntityUtilityBus.Broadcast.FindMatchingComponents("Transform*")
)LUA");
// There should be 2 transform components
EXPECT_EQ(g_globalComponentDetails.size(), 2);
}
TEST_F(EntityUtilityComponentTests, SearchComponentsNotFound)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
g_globalComponentDetails = EntityUtilityBus.Broadcast.FindMatchingComponents("404")
)LUA");
EXPECT_EQ(g_globalComponentDetails.size(), 0);
}
}
@@ -25,7 +25,7 @@ namespace UnitTest
R"X(Executing RC.EXE: '"E:\lyengine\dev\windows\bin\profile\rc.exe" "E:/Directory/File.tga")X",
R"X(Executing RC.EXE with working directory : '')X",
R"X(ResourceCompiler 64 - bit DEBUG)X",
R"X(Platform support : PC, PowerVR, etc2Comp)X",
R"X(Platform support : PC, PowerVR)X",
R"X(Version 1.1.8.6 Nov 5 2018 13 : 28 : 28)X"
};
@@ -0,0 +1,173 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <Entity/EntityUtilityComponent.h>
#include <Prefab/PrefabTestComponent.h>
#include <Prefab/PrefabTestDomUtils.h>
#include <Prefab/PrefabTestFixture.h>
namespace UnitTest
{
TemplateId g_globalTemplateId = {};
AZStd::string g_globalPrefabString = "";
class PrefabScriptingTest : public PrefabTestFixture
{
void InitProperties() const
{
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
ASSERT_NE(componentApplicationRequests, nullptr);
auto behaviorContext = componentApplicationRequests->GetBehaviorContext();
ASSERT_NE(behaviorContext, nullptr);
behaviorContext->Property("g_globalTemplateId", BehaviorValueProperty(&g_globalTemplateId));
behaviorContext->Property("g_globalPrefabString", BehaviorValueProperty(&g_globalPrefabString));
g_globalTemplateId = TemplateId{};
g_globalPrefabString = AZStd::string{};
}
void SetUpEditorFixtureImpl() override
{
InitProperties();
}
void TearDownEditorFixtureImpl() override
{
g_globalPrefabString.set_capacity(0); // Free all memory
}
};
TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
entities = vector_EntityId()
entities:push_back(my_id)
g_globalTemplateId = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "test.prefab")
)LUA");
EXPECT_NE(g_globalTemplateId, TemplateId{});
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
ASSERT_NE(prefabSystemComponentInterface, nullptr);
TemplateReference templateRef = prefabSystemComponentInterface->FindTemplate(g_globalTemplateId);
EXPECT_TRUE(templateRef);
}
TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab_NoEntities)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
entities = vector_EntityId()
g_globalTemplateId = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "test.prefab")
)LUA");
EXPECT_NE(g_globalTemplateId, TemplateId{});
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
ASSERT_NE(prefabSystemComponentInterface, nullptr);
TemplateReference templateRef = prefabSystemComponentInterface->FindTemplate(g_globalTemplateId);
EXPECT_TRUE(templateRef);
}
TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab_NoPath)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
AZ_TEST_START_TRACE_SUPPRESSION;
sc.Execute(R"LUA(
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
entities = vector_EntityId()
template_id = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "")
)LUA");
/*
error: PrefabSystemComponent::CreateTemplateFromInstance - Attempted to create a prefab template from an instance without a source file path. Unable to proceed.
error: Failed to create a Template associated with file path during CreatePrefab.
error: Failed to create prefab
*/
AZ_TEST_STOP_TRACE_SUPPRESSION(3);
}
TEST_F(PrefabScriptingTest, PrefabScripting_SaveToString)
{
AZ::ScriptContext sc;
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
sc.BindTo(behaviorContext);
sc.Execute(R"LUA(
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
entities = vector_EntityId()
entities:push_back(my_id)
template_id = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "test.prefab")
my_result = PrefabLoaderScriptingBus.Broadcast.SaveTemplateToString(template_id)
if my_result:IsSuccess() then
g_globalPrefabString = my_result:GetValue()
end
)LUA");
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
prefabSystemComponentInterface->RemoveAllTemplates();
EXPECT_STRNE(g_globalPrefabString.c_str(), "");
TemplateId templateFromString = AZ::Interface<PrefabLoaderInterface>::Get()->LoadTemplateFromString(g_globalPrefabString);
EXPECT_NE(templateFromString, InvalidTemplateId);
// Create another entity for comparison purposes
AZ::EntityId entityId;
AzToolsFramework::EntityUtilityBus::BroadcastResult(
entityId, &AzToolsFramework::EntityUtilityBus::Events::CreateEditorReadyEntity, "test");
AZ::Entity* testEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entityId);
// Instantiate the prefab we saved
AZStd::unique_ptr<Instance> instance = prefabSystemComponentInterface->InstantiatePrefab(templateFromString);
EXPECT_NE(instance, nullptr);
AZStd::vector<const AZ::Entity*> loadedEntities;
// Get the entities from the instance
instance->GetConstEntities(
[&loadedEntities](const AZ::Entity& entity)
{
loadedEntities.push_back(&entity);
return true;
});
// Make sure the instance has an entity with the same number of components as our test entity
EXPECT_EQ(loadedEntities.size(), 1);
EXPECT_EQ(loadedEntities[0]->GetComponents().size(), testEntity->GetComponents().size());
g_globalPrefabString.set_capacity(0); // Free all memory
}
}
@@ -0,0 +1,135 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <Entity/EntityUtilityComponent.h>
#include <Prefab/PrefabTestComponent.h>
#include <Prefab/PrefabTestDomUtils.h>
#include <Prefab/PrefabTestFixture.h>
#include <Prefab/Procedural/ProceduralPrefabAsset.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
namespace UnitTest
{
class ProceduralPrefabAssetTest
: public PrefabTestFixture
{
void SetUpEditorFixtureImpl() override
{
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
ASSERT_NE(componentApplicationRequests, nullptr);
auto* behaviorContext = componentApplicationRequests->GetBehaviorContext();
ASSERT_NE(behaviorContext, nullptr);
auto* jsonRegistrationContext = componentApplicationRequests->GetJsonRegistrationContext();
ASSERT_NE(jsonRegistrationContext, nullptr);
auto* serializeContext = componentApplicationRequests->GetSerializeContext();
ASSERT_NE(serializeContext, nullptr);
AZ::Prefab::ProceduralPrefabAsset::Reflect(serializeContext);
AZ::Prefab::ProceduralPrefabAsset::Reflect(behaviorContext);
AZ::Prefab::ProceduralPrefabAsset::Reflect(jsonRegistrationContext);
}
void TearDownEditorFixtureImpl() override
{
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
componentApplicationRequests->GetJsonRegistrationContext()->EnableRemoveReflection();
AZ::Prefab::ProceduralPrefabAsset::Reflect(componentApplicationRequests->GetJsonRegistrationContext());
}
};
TEST_F(ProceduralPrefabAssetTest, ReflectContext_AccessMethods_Works)
{
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
auto* serializeContext = componentApplicationRequests->GetSerializeContext();
EXPECT_TRUE(!serializeContext->CreateAny(azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>()).empty());
EXPECT_TRUE(!serializeContext->CreateAny(azrtti_typeid<AZ::Prefab::PrefabDomData>()).empty());
auto* jsonRegistrationContext = componentApplicationRequests->GetJsonRegistrationContext();
EXPECT_TRUE(jsonRegistrationContext->GetSerializerForSerializerType(azrtti_typeid<AZ::Prefab::PrefabDomDataJsonSerializer>()));
}
TEST_F(ProceduralPrefabAssetTest, ProceduralPrefabAsset_AccessMethods_Works)
{
const auto templateId = TemplateId(1);
const auto prefabString = "fake.prefab";
AZ::Prefab::ProceduralPrefabAsset asset{};
asset.SetTemplateId(templateId);
EXPECT_EQ(asset.GetTemplateId(), templateId);
asset.SetTemplateName(prefabString);
EXPECT_EQ(asset.GetTemplateName(), prefabString);
}
TEST_F(ProceduralPrefabAssetTest, PrefabDomData_AccessMethods_Works)
{
AzToolsFramework::Prefab::PrefabDom dom;
dom.SetObject();
dom.AddMember("boolValue", true, dom.GetAllocator());
AZ::Prefab::PrefabDomData prefabDomData;
prefabDomData.CopyValue(dom);
const AzToolsFramework::Prefab::PrefabDom& result = prefabDomData.GetValue();
EXPECT_TRUE(result.HasMember("boolValue"));
EXPECT_TRUE(result.FindMember("boolValue")->value.GetBool());
}
TEST_F(ProceduralPrefabAssetTest, PrefabDomDataJsonSerializer_Load_Works)
{
AZ::Prefab::PrefabDomData prefabDomData;
AzToolsFramework::Prefab::PrefabDom dom;
dom.SetObject();
dom.AddMember("member", "value", dom.GetAllocator());
AZ::Prefab::PrefabDomDataJsonSerializer prefabDomDataJsonSerializer;
AZ::JsonDeserializerSettings settings;
settings.m_reporting = [](auto, auto, auto)
{
AZ::JsonSerializationResult::ResultCode result(AZ::JsonSerializationResult::Tasks::ReadField);
return result;
};
AZ::JsonDeserializerContext context{ settings };
auto result = prefabDomDataJsonSerializer.Load(&prefabDomData, azrtti_typeid(prefabDomData), dom, context);
EXPECT_EQ(result.GetResultCode().GetOutcome(), AZ::JsonSerializationResult::Outcomes::DefaultsUsed);
EXPECT_TRUE(prefabDomData.GetValue().HasMember("member"));
EXPECT_STREQ(prefabDomData.GetValue().FindMember("member")->value.GetString(), "value");
}
TEST_F(ProceduralPrefabAssetTest, PrefabDomDataJsonSerializer_Store_Works)
{
AzToolsFramework::Prefab::PrefabDom dom;
dom.SetObject();
dom.AddMember("member", "value", dom.GetAllocator());
AZ::Prefab::PrefabDomData prefabDomData;
prefabDomData.CopyValue(dom);
AZ::Prefab::PrefabDomDataJsonSerializer prefabDomDataJsonSerializer;
AzToolsFramework::Prefab::PrefabDom outputValue;
AZ::JsonSerializerSettings settings;
settings.m_reporting = [](auto, auto, auto)
{
AZ::JsonSerializationResult::ResultCode result(AZ::JsonSerializationResult::Tasks::WriteValue);
return result;
};
AZ::JsonSerializerContext context{ settings, outputValue.GetAllocator() };
auto result = prefabDomDataJsonSerializer.Store(outputValue, &prefabDomData, nullptr, azrtti_typeid(prefabDomData), context);
EXPECT_EQ(result.GetResultCode().GetOutcome(), AZ::JsonSerializationResult::Outcomes::DefaultsUsed);
EXPECT_TRUE(outputValue.HasMember("member"));
EXPECT_STREQ(outputValue.FindMember("member")->value.GetString(), "value");
}
}
@@ -27,6 +27,7 @@ set(FILES
Entity/EditorEntityHelpersTests.cpp
Entity/EditorEntitySearchComponentTests.cpp
Entity/EditorEntitySelectionTests.cpp
Entity/EntityUtilityComponentTests.cpp
EntityIdQLabelTests.cpp
EntityInspectorTests.cpp
EntityOwnershipService/EntityOwnershipServiceTestFixture.cpp
@@ -91,6 +92,8 @@ set(FILES
Prefab/SpawnableSortEntitiesTestFixture.cpp
Prefab/SpawnableSortEntitiesTestFixture.h
Prefab/SpawnableSortEntitiesTests.cpp
Prefab/PrefabScriptingTests.cpp
Prefab/ProceduralPrefabAssetTests.cpp
PropertyIntCtrlCommonTests.h
PropertyIntSliderCtrlTests.cpp
PropertyIntSpinCtrlTests.cpp