merge from main

This commit is contained in:
greerdv
2021-05-24 11:27:43 +01:00
640 changed files with 15110 additions and 14080 deletions
@@ -133,7 +133,15 @@ namespace AZ
if (!id.m_guid.IsNull())
{
*instance = AssetManager::Instance().FindOrCreateAsset(id, instance->GetType(), instance->GetAutoLoadBehavior());
if (!instance->GetId().IsValid())
{
// If the asset failed to be created, FindOrCreateAsset returns an asset instance with a null
// id. To preserve the asset id in the source json, reset the asset to an empty one, but with
// the right id.
const auto loadBehavior = instance->GetAutoLoadBehavior();
*instance = Asset<AssetData>(id, instance->GetType());
instance->SetAutoLoadBehavior(loadBehavior);
}
result.Combine(context.Report(result, "Successfully created Asset<T> with id."));
}
@@ -86,4 +86,94 @@ namespace AZ
Normal,
UniformReal
};
//! Halton sequences are deterministic, quasi-random sequences with low discrepancy. They
//! are useful for generating evenly distributed points.
//! See https://en.wikipedia.org/wiki/Halton_sequence for more information.
//! Returns a single halton number.
//! @param index The index of the number. Indices start at 1. Using index 0 will return 0.
//! @param base The numerical base of the halton number.
inline float GetHaltonNumber(uint32_t index, uint32_t base)
{
float fraction = 1.0f;
float result = 0.0f;
while (index > 0)
{
fraction = fraction / base;
result += fraction * (index % base);
index = aznumeric_cast<uint32_t>(index / base);
}
return result;
}
//! A helper class for generating arrays of Halton sequences in n dimensions.
//! The class holds the state of which bases to use, the starting offset
//! of each dimension and how much to increment between each index for each
//! dimension.
template <uint8_t Dimensions>
class HaltonSequence
{
public:
//! Initializes a Halton sequence with some bases. By default there is no
//! offset and the index increments by 1 between each number.
HaltonSequence(AZStd::array<uint32_t, Dimensions> bases)
: m_bases(bases)
{
m_offsets.fill(1); // Halton sequences start at index 1.
m_increments.fill(1); // By default increment by 1 between each number.
}
//! Returns a Halton sequence in an array of N length
template<uint32_t N>
AZStd::array<AZStd::array<float, Dimensions>, N> GetHaltonSequence()
{
AZStd::array<AZStd::array<float, Dimensions>, N> result;
AZStd::array<uint32_t, Dimensions> indices = m_offsets;
// Generator that returns the Halton number for all bases for a single entry.
auto f = [&] ()
{
AZStd::array<float, Dimensions> item;
for (auto d = 0; d < Dimensions; ++d)
{
item[d] = GetHaltonNumber(indices[d], m_bases[d]);
indices[d] += m_increments[d];
}
return item;
};
AZStd::generate(result.begin(), result.end(), f);
return result;
}
//! Sets the offsets per dimension to start generating a sequence from.
//! By default, there is no offset (offset of 0 corresponds to starting at index 1)
void SetOffsets(AZStd::array<uint32_t, Dimensions> offsets)
{
m_offsets = offsets;
// Halton sequences start at index 1, so increment all the indices.
AZStd::for_each(m_offsets.begin(), m_offsets.end(), [](uint32_t &n){ n++; });
}
//! Sets the increment between numbers in the halton sequence per dimension
//! By default this is 1, meaning that no numbers are skipped. Can be negative
//! to generate numbers in reverse order.
void SetIncrements(AZStd::array<int32_t, Dimensions> increments)
{
m_increments = increments;
}
private:
AZStd::array<uint32_t, Dimensions> m_bases;
AZStd::array<uint32_t, Dimensions> m_offsets;
AZStd::array<int32_t, Dimensions> m_increments;
};
}
@@ -14,6 +14,7 @@
#include <cerrno>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/JSON/error/en.h>
#include <AzCore/NativeUI//NativeUIRequests.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/StackedString.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
@@ -880,6 +881,13 @@ namespace AZ
const Specializations& specializations, const rapidjson::Pointer& historyPointer, AZStd::string_view folderPath)
{
using namespace rapidjson;
if (&lhs == &rhs)
{
// Early return to avoid setting the collisionFound reference to true
// std::sort is allowed to pass in the same memory address for the left and right elements
return false;
}
AZ_Assert(!lhs.m_tags.empty(), "Comparing a settings file without at least a name tag.");
AZ_Assert(!rhs.m_tags.empty(), "Comparing a settings file without at least a name tag.");
@@ -1054,15 +1062,23 @@ namespace AZ
jsonPatch.ParseInsitu<flags>(scratchBuffer.data());
if (jsonPatch.HasParseError())
{
auto nativeUI = AZ::Interface<NativeUI::NativeUIRequests>::Get();
if (jsonPatch.GetParseError() == rapidjson::kParseErrorDocumentEmpty)
{
AZ_Warning("Settings Registry", false, R"(Unable to parse registry file "%s" due to json error "%s" at offset %llu.)",
AZ_Warning("Settings Registry", false, R"(Unable to parse registry file "%s" due to json error "%s" at offset %zu.)",
path, GetParseError_En(jsonPatch.GetParseError()), jsonPatch.GetErrorOffset());
}
else
{
AZ_Error("Settings Registry", false, R"(Unable to parse registry file "%s" due to json error "%s" at offset %llu.)", path,
using ErrorString = AZStd::fixed_string<4096>;
auto jsonError = ErrorString::format(R"(Unable to parse registry file "%s" due to json error "%s" at offset %zu.)", path,
GetParseError_En(jsonPatch.GetParseError()), jsonPatch.GetErrorOffset());
AZ_Error("Settings Registry", false, "%s", jsonError.c_str());
if (nativeUI)
{
nativeUI->DisplayOkDialog("Setreg(Patch) Merge Issue", AZStd::string_view(jsonError), false);
}
}
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
@@ -41,10 +41,6 @@ namespace AZ
if (const char* homePath = std::getenv("HOME"); homePath != nullptr)
{
AZ::IO::FixedMaxPath path{homePath};
if (!path.empty())
{
path /= ".o3de";
}
return path.Native();
}
return {};
@@ -0,0 +1,74 @@
/*
* 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/Math/Random.h>
#include <AzCore/UnitTest/TestTypes.h>
using namespace AZ;
namespace UnitTest
{
TEST(MATH_Random, GetHaltonNumber)
{
EXPECT_FLOAT_EQ(0.5, GetHaltonNumber(1, 2));
EXPECT_FLOAT_EQ(898.0f / 2187.0f, GetHaltonNumber(1234, 3));
EXPECT_FLOAT_EQ(5981.0f / 15625.0f, GetHaltonNumber(4321, 5));
}
TEST(MATH_Random, HaltonSequence)
{
HaltonSequence<3> sequence({ 2, 3, 5 });
auto regularSequence = sequence.GetHaltonSequence<5>();
EXPECT_FLOAT_EQ(1.0f / 2.0f, regularSequence[0][0]);
EXPECT_FLOAT_EQ(1.0f / 3.0f, regularSequence[0][1]);
EXPECT_FLOAT_EQ(1.0f / 5.0f, regularSequence[0][2]);
EXPECT_FLOAT_EQ(1.0f / 4.0f, regularSequence[1][0]);
EXPECT_FLOAT_EQ(2.0f / 3.0f, regularSequence[1][1]);
EXPECT_FLOAT_EQ(2.0f / 5.0f, regularSequence[1][2]);
EXPECT_FLOAT_EQ(3.0f / 4.0f, regularSequence[2][0]);
EXPECT_FLOAT_EQ(1.0f / 9.0f, regularSequence[2][1]);
EXPECT_FLOAT_EQ(3.0f / 5.0f, regularSequence[2][2]);
EXPECT_FLOAT_EQ(1.0f / 8.0f, regularSequence[3][0]);
EXPECT_FLOAT_EQ(4.0f / 9.0f, regularSequence[3][1]);
EXPECT_FLOAT_EQ(4.0f / 5.0f, regularSequence[3][2]);
EXPECT_FLOAT_EQ(5.0f / 8.0f, regularSequence[4][0]);
EXPECT_FLOAT_EQ(7.0f / 9.0f, regularSequence[4][1]);
EXPECT_FLOAT_EQ(1.0f / 25.0f, regularSequence[4][2]);
sequence.SetOffsets({ 1, 2, 3 });
auto offsetSequence = sequence.GetHaltonSequence<2>();
EXPECT_FLOAT_EQ(1.0f / 4.0f, offsetSequence[0][0]);
EXPECT_FLOAT_EQ(1.0f / 9.0f, offsetSequence[0][1]);
EXPECT_FLOAT_EQ(4.0f / 5.0f, offsetSequence[0][2]);
EXPECT_FLOAT_EQ(3.0f / 4.0f, offsetSequence[1][0]);
EXPECT_FLOAT_EQ(4.0f / 9.0f, offsetSequence[1][1]);
EXPECT_FLOAT_EQ(1.0f / 25.0f, offsetSequence[1][2]);
sequence.SetIncrements({ 1, 2, 3 });
auto incrementedSequence = sequence.GetHaltonSequence<2>();
EXPECT_FLOAT_EQ(1.0f / 4.0f, incrementedSequence[0][0]);
EXPECT_FLOAT_EQ(1.0f / 9.0f, incrementedSequence[0][1]);
EXPECT_FLOAT_EQ(4.0f / 5.0f, incrementedSequence[0][2]);
EXPECT_FLOAT_EQ(3.0f / 4.0f, incrementedSequence[1][0]);
EXPECT_FLOAT_EQ(7.0f / 9.0f, incrementedSequence[1][1]);
EXPECT_FLOAT_EQ(11.0f / 25.0f, incrementedSequence[1][2]);
}
}
@@ -152,6 +152,7 @@ set(FILES
Math/PlaneTests.cpp
Math/QuaternionPerformanceTests.cpp
Math/QuaternionTests.cpp
Math/RandomTests.cpp
Math/ShapeIntersectionPerformanceTests.cpp
Math/ShapeIntersectionTests.cpp
Math/SfmtTests.cpp
@@ -30,6 +30,16 @@ namespace AzPhysics
classElement.AddElementWithData(context, "name", name);
return true;
}
bool SimulatedBodyVersionConverter([[maybe_unused]] AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
{
if (classElement.GetVersion() <= 1)
{
classElement.RemoveElementByName(AZ_CRC_CE("scale"));
}
return true;
}
}
AZ_CLASS_ALLOCATOR_IMPL(SimulatedBodyConfiguration, AZ::SystemAllocator, 0);
@@ -40,11 +50,10 @@ namespace AzPhysics
{
serializeContext->ClassDeprecate("WorldBodyConfiguration", "{6EEB377C-DC60-4E10-AF12-9626C0763B2D}", &Internal::DeprecateWorldBodyConfiguration);
serializeContext->Class<SimulatedBodyConfiguration>()
->Version(1)
->Version(2, &Internal::SimulatedBodyVersionConverter)
->Field("name", &SimulatedBodyConfiguration::m_debugName)
->Field("position", &SimulatedBodyConfiguration::m_position)
->Field("orientation", &SimulatedBodyConfiguration::m_orientation)
->Field("scale", &SimulatedBodyConfiguration::m_scale)
->Field("entityId", &SimulatedBodyConfiguration::m_entityId)
->Field("startSimulationEnabled", &SimulatedBodyConfiguration::m_startSimulationEnabled)
;
@@ -38,7 +38,6 @@ namespace AzPhysics
// Basic initial settings.
AZ::Vector3 m_position = AZ::Vector3::CreateZero();
AZ::Quaternion m_orientation = AZ::Quaternion::CreateIdentity();
AZ::Vector3 m_scale = AZ::Vector3::CreateOne();
bool m_startSimulationEnabled = true;
// Entity/object association.
@@ -21,7 +21,7 @@ namespace AzPhysics
namespace
{
const float TimestepMin = 0.001f; //1000fps
const float TimestepMax = 0.05f; //20fps
const float TimestepMax = 0.1f; //10fps
}
AZ_CLASS_ALLOCATOR_IMPL(SystemConfiguration, AZ::SystemAllocator, 0);
@@ -34,7 +34,7 @@ namespace AzPhysics
static constexpr float DefaultFixedTimestep = 0.0166667f; //! Value represents 1/60th or 60 FPS.
float m_maxTimestep = 1.f / 20.f; //!< Maximum fixed timestep in seconds to run the physics update.
float m_maxTimestep = 0.1f; //!< Maximum fixed timestep in seconds to run the physics update (10FPS).
float m_fixedTimestep = DefaultFixedTimestep; //!< Timestep in seconds to run the physics update. See DefaultFixedTimestep.
AZ::u64 m_raycastBufferSize = 32; //!< Maximum number of hits that will be returned from a raycast.
@@ -0,0 +1,78 @@
/*
* 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/std/string/string.h>
namespace AzFramework
{
//! SessionConnectionConfig
//! The properties for handling join session request.
struct SessionConnectionConfig
{
// A unique identifier for registered player in session.
AZStd::string m_playerSessionId;
// The DNS identifier assigned to the instance that is running the session.
AZStd::string m_dnsName;
// The IP address of the session.
AZStd::string m_ipAddress;
// The port number for the session.
uint16_t m_port;
};
//! SessionConnectionConfig
//! The properties for handling player connect/disconnect
struct PlayerConnectionConfig
{
// A unique identifier for player connection.
uint32_t m_playerConnectionId;
// A unique identifier for registered player in session.
AZStd::string m_playerSessionId;
};
//! ISessionHandlingClientRequests
//! The session handling events to invoke multiplayer component handle the work on client side
class ISessionHandlingClientRequests
{
public:
// Handle the player join session process
// @param sessionConnectionConfig The required properties to handle the player join session process
// @return The result of player join session process
virtual bool HandlePlayerJoinSession(const SessionConnectionConfig& sessionConnectionConfig) = 0;
// Handle the player leave session process
virtual void HandlePlayerLeaveSession() = 0;
};
//! ISessionHandlingServerRequests
//! The session handling events to invoke server provider handle the work on server side
class ISessionHandlingServerRequests
{
public:
// Handle the destroy session process
virtual void HandleDestroySession() = 0;
// Validate the player join session process
// @param playerConnectionConfig The required properties to validate the player join session process
// @return The result of player join session validation
virtual bool ValidatePlayerJoinSession(const PlayerConnectionConfig& playerConnectionConfig) = 0;
// Handle the player leave session process
// @param playerConnectionConfig The required properties to handle the player leave session process
virtual void HandlePlayerLeaveSession(const PlayerConnectionConfig& playerConnectionConfig) = 0;
};
} // namespace AzFramework
@@ -0,0 +1,130 @@
/*
* 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/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Session/ISessionRequests.h>
#include <AzFramework/Session/SessionConfig.h>
namespace AzFramework
{
void CreateSessionRequest::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<CreateSessionRequest>()
->Version(0)
->Field("creatorId", &CreateSessionRequest::m_creatorId)
->Field("sessionProperties", &CreateSessionRequest::m_sessionProperties)
->Field("sessionName", &CreateSessionRequest::m_sessionName)
->Field("maxPlayer", &CreateSessionRequest::m_maxPlayer)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<CreateSessionRequest>("CreateSessionRequest", "The container for CreateSession request parameters")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_creatorId,
"CreatorId", "A unique identifier for a player or entity creating the session")
->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_sessionProperties,
"SessionProperties", "A collection of custom properties for a session")
->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_sessionName,
"SessionName", "A descriptive label that is associated with a session")
->DataElement(AZ::Edit::UIHandlers::Default, &CreateSessionRequest::m_maxPlayer,
"MaxPlayer", "The maximum number of players that can be connected simultaneously to the session")
;
}
}
}
void SearchSessionsRequest::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SearchSessionsRequest>()
->Version(0)
->Field("filterExpression", &SearchSessionsRequest::m_filterExpression)
->Field("sortExpression", &SearchSessionsRequest::m_sortExpression)
->Field("maxResult", &SearchSessionsRequest::m_maxResult)
->Field("nextToken", &SearchSessionsRequest::m_nextToken)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<SearchSessionsRequest>("SearchSessionsRequest", "The container for SearchSessions request parameters")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_filterExpression,
"FilterExpression", "String containing the search criteria for the session search")
->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_sortExpression,
"SortExpression", "Instructions on how to sort the search results")
->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_maxResult,
"MaxResult", "The maximum number of results to return")
->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsRequest::m_nextToken,
"NextToken", "A token that indicates the start of the next sequential page of results")
;
}
}
}
void SearchSessionsResponse::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SearchSessionsResponse>()
->Version(0)
->Field("sessionConfigs", &SearchSessionsResponse::m_sessionConfigs)
->Field("nextToken", &SearchSessionsResponse::m_nextToken)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<SearchSessionsResponse>("SearchSessionsResponse", "The container for SearchSession request results")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsResponse::m_sessionConfigs,
"SessionConfigs", "A collection of sessions that match the search criteria and sorted in specific order")
->DataElement(AZ::Edit::UIHandlers::Default, &SearchSessionsResponse::m_nextToken,
"NextToken", "A token that indicates the start of the next sequential page of results")
;
}
}
}
void JoinSessionRequest::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<JoinSessionRequest>()
->Version(0)
->Field("sessionId", &JoinSessionRequest::m_sessionId)
->Field("playerId", &JoinSessionRequest::m_playerId)
->Field("playerData", &JoinSessionRequest::m_playerData)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<JoinSessionRequest>("JoinSessionRequest", "The container for JoinSession request parameters")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &JoinSessionRequest::m_sessionId,
"SessionId", "A unique identifier for the session")
->DataElement(AZ::Edit::UIHandlers::Default, &JoinSessionRequest::m_playerId,
"PlayerId", "A unique identifier for a player. Player IDs are developer-defined")
->DataElement(AZ::Edit::UIHandlers::Default, &JoinSessionRequest::m_playerData,
"PlayerData", "Developer-defined information related to a player")
;
}
}
}
} // namespace AzFramework
@@ -0,0 +1,192 @@
/*
* 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/EBus/EBus.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <AzCore/Outcome/Outcome.h>
namespace AzFramework
{
struct SessionConfig;
//! CreateSessionRequest
//! The container for CreateSession request parameters.
struct CreateSessionRequest
{
AZ_RTTI(CreateSessionRequest, "{E39C2A45-89C9-4CFB-B337-9734DC798930}");
static void Reflect(AZ::ReflectContext* context);
CreateSessionRequest() = default;
virtual ~CreateSessionRequest() = default;
// A unique identifier for a player or entity creating the session.
AZStd::string m_creatorId;
// A collection of custom properties for a session.
AZStd::unordered_map<AZStd::string, AZStd::string> m_sessionProperties;
// A descriptive label that is associated with a session.
AZStd::string m_sessionName;
// The maximum number of players that can be connected simultaneously to the session.
uint64_t m_maxPlayer;
};
//! SearchSessionsRequest
//! The container for SearchSessions request parameters.
struct SearchSessionsRequest
{
AZ_RTTI(SearchSessionsRequest, "{B49207A8-8549-4ADB-B7D9-D7A4932F9B4B}");
static void Reflect(AZ::ReflectContext* context);
SearchSessionsRequest() = default;
virtual ~SearchSessionsRequest() = default;
// String containing the search criteria for the session search. If no filter expression is included, the request returns results
// for all active sessions.
AZStd::string m_filterExpression;
// Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order.
AZStd::string m_sortExpression;
// The maximum number of results to return.
uint8_t m_maxResult;
// A token that indicates the start of the next sequential page of results.
AZStd::string m_nextToken;
};
//! SearchSessionsResponse
//! The container for SearchSession request results.
struct SearchSessionsResponse
{
AZ_RTTI(SearchSessionsResponse, "{F93DE7DC-D381-4E08-8A3B-0B08F7C38714}");
static void Reflect(AZ::ReflectContext* context);
SearchSessionsResponse() = default;
virtual ~SearchSessionsResponse() = default;
// A collection of sessions that match the search criteria and sorted in specific order.
AZStd::vector<SessionConfig> m_sessionConfigs;
// A token that indicates the start of the next sequential page of results.
AZStd::string m_nextToken;
};
//! JoinSessionRequest
//! The container for JoinSession request parameters.
struct JoinSessionRequest
{
AZ_RTTI(JoinSessionRequest, "{519769E8-3CDE-4385-A0D7-24DBB3685657}");
static void Reflect(AZ::ReflectContext* context);
JoinSessionRequest() = default;
virtual ~JoinSessionRequest() = default;
// A unique identifier for the session.
AZStd::string m_sessionId;
// A unique identifier for a player. Player IDs are developer-defined.
AZStd::string m_playerId;
// Developer-defined information related to a player.
AZStd::string m_playerData;
};
//! ISessionRequests
//! Pure virtual session interface class to abstract the details of session handling from application code.
class ISessionRequests
{
public:
AZ_RTTI(ISessionRequests, "{D6C41A71-DD8D-47FE-8515-FAF90670AE2F}");
ISessionRequests() = default;
virtual ~ISessionRequests() = default;
// Create a session for players to find and join.
// @param createSessionRequest The request of CreateSession operation
// @return The request id if session creation request succeeds; empty if it fails
virtual AZStd::string CreateSession(const CreateSessionRequest& createSessionRequest) = 0;
// Retrieve all active sessions that match the given search criteria and sorted in specific order.
// @param searchSessionsRequest The request of SearchSessions operation
// @return The response of SearchSessions operation
virtual SearchSessionsResponse SearchSessions(const SearchSessionsRequest& searchSessionsRequest) const = 0;
// Reserve an open player slot in a session, and perform connection from client to server.
// @param joinSessionRequest The request of JoinSession operation
// @return True if joining session succeeds; False otherwise
virtual bool JoinSession(const JoinSessionRequest& joinSessionRequest) = 0;
// Disconnect player from session.
virtual void LeaveSession() = 0;
};
//! ISessionAsyncRequests
//! Async version of ISessionRequests
class ISessionAsyncRequests
{
public:
AZ_RTTI(ISessionAsyncRequests, "{471542AF-96B9-4930-82FE-242A4E68432D}");
ISessionAsyncRequests() = default;
virtual ~ISessionAsyncRequests() = default;
// CreateSession Async
// @param createSessionRequest The request of CreateSession operation
virtual void CreateSessionAsync(const CreateSessionRequest& createSessionRequest) = 0;
// SearchSessions Async
// @param searchSessionsRequest The request of SearchSessions operation
virtual void SearchSessionsAsync(const SearchSessionsRequest& searchSessionsRequest) const = 0;
// JoinSession Async
// @param joinSessionRequest The request of JoinSession operation
virtual void JoinSessionAsync(const JoinSessionRequest& joinSessionRequest) = 0;
// LeaveSession Async
virtual void LeaveSessionAsync() = 0;
};
//! SessionAsyncRequestNotifications
//! The notifications correspond to session async requests
class SessionAsyncRequestNotifications
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnCreateSessionAsyncComplete is fired once CreateSessionAsync completes
// @param createSessionResponse The request id if session creation request succeeds; empty if it fails
virtual void OnCreateSessionAsyncComplete(const AZStd::string& createSessionReponse) = 0;
// OnSearchSessionsAsyncComplete is fired once SearchSessionsAsync completes
// @param searchSessionsResponse The response of SearchSessions call
virtual void OnSearchSessionsAsyncComplete(const SearchSessionsResponse& searchSessionsResponse) = 0;
// OnJoinSessionAsyncComplete is fired once JoinSessionAsync completes
// @param joinSessionsResponse True if joining session succeeds; False otherwise
virtual void OnJoinSessionAsyncComplete(bool joinSessionsResponse) = 0;
// OnLeaveSessionAsyncComplete is fired once LeaveSessionAsync completes
virtual void OnLeaveSessionAsyncComplete() = 0;
};
using SessionAsyncRequestNotificationBus = AZ::EBus<SessionAsyncRequestNotifications>;
} // namespace AzFramework
@@ -0,0 +1,74 @@
/*
* 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/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Session/SessionConfig.h>
namespace AzFramework
{
void SessionConfig::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SessionConfig>()
->Version(0)
->Field("creationTime", &SessionConfig::m_creationTime)
->Field("terminationTime", &SessionConfig::m_terminationTime)
->Field("creatorId", &SessionConfig::m_creatorId)
->Field("sessionProperties", &SessionConfig::m_sessionProperties)
->Field("sessionId", &SessionConfig::m_sessionId)
->Field("sessionName", &SessionConfig::m_sessionName)
->Field("dnsName", &SessionConfig::m_dnsName)
->Field("ipAddress", &SessionConfig::m_ipAddress)
->Field("port", &SessionConfig::m_port)
->Field("maxPlayer", &SessionConfig::m_maxPlayer)
->Field("currentPlayer", &SessionConfig::m_currentPlayer)
->Field("status", &SessionConfig::m_status)
->Field("statusReason", &SessionConfig::m_statusReason)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<SessionConfig>("SessionConfig", "Properties describing a session")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_creationTime,
"CreationTime", "A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_terminationTime,
"TerminationTime", "A time stamp indicating when this data object was terminated. Same format as creation time.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_creatorId,
"CreatorId", "A unique identifier for a player or entity creating the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionProperties,
"SessionProperties", "A collection of custom properties for a session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionId,
"SessionId", "A unique identifier for the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionName,
"SessionName", "A descriptive label that is associated with a session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_dnsName,
"DnsName", "The DNS identifier assigned to the instance that is running the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_ipAddress,
"IpAddress", "The IP address of the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_port,
"Port", "The port number for the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_maxPlayer,
"MaxPlayer", "The maximum number of players that can be connected simultaneously to the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_currentPlayer,
"CurrentPlayer", "Number of players currently in the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_status,
"Status", "Current status of the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_statusReason,
"StatusReason", "Provides additional information about session status.");
}
}
}
} // namespace AzFramework
@@ -0,0 +1,70 @@
/*
* 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/ReflectContext.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
namespace AzFramework
{
//! SessionConfig
//! Properties describing a session.
struct SessionConfig
{
AZ_RTTI(SessionConfig, "{992DD4BE-8BA5-4071-8818-B99FD2952086}");
static void Reflect(AZ::ReflectContext* context);
SessionConfig() = default;
virtual ~SessionConfig() = default;
// A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds.
uint64_t m_creationTime;
// A time stamp indicating when this data object was terminated. Same format as creation time.
uint64_t m_terminationTime;
// A unique identifier for a player or entity creating the session.
AZStd::string m_creatorId;
// A collection of custom properties for a session.
AZStd::unordered_map<AZStd::string, AZStd::string> m_sessionProperties;
// A unique identifier for the session.
AZStd::string m_sessionId;
// A descriptive label that is associated with a session.
AZStd::string m_sessionName;
// The DNS identifier assigned to the instance that is running the session.
AZStd::string m_dnsName;
// The IP address of the session.
AZStd::string m_ipAddress;
// The port number for the session.
uint16_t m_port;
// The maximum number of players that can be connected simultaneously to the session.
uint64_t m_maxPlayer;
// Number of players currently in the session.
uint64_t m_currentPlayer;
// Current status of the session.
AZStd::string m_status;
// Provides additional information about session status.
AZStd::string m_statusReason;
};
} // namespace AzFramework
@@ -0,0 +1,47 @@
/*
* 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/EBus/EBus.h>
namespace AzFramework
{
struct SessionConfig;
//! SessionNotifications
//! The session notifications to listen for performing required operations
class SessionNotifications
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnSessionHealthCheck is fired in health check process
// @return The result of all OnSessionHealthCheck
virtual bool OnSessionHealthCheck() = 0;
// OnCreateSessionBegin is fired at the beginning of session creation
// @param sessionConfig The properties to describe a session
// @return The result of all OnCreateSessionBegin notifications
virtual bool OnCreateSessionBegin(const SessionConfig& sessionConfig) = 0;
// OnDestroySessionBegin is fired at the beginning of session termination
// @return The result of all OnDestroySessionBegin notifications
virtual bool OnDestroySessionBegin() = 0;
};
using SessionNotificationBus = AZ::EBus<SessionNotifications>;
} // namespace AzFramework
@@ -11,6 +11,7 @@
*/
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Serialization/IdUtils.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/parallel/scoped_lock.h>
#include <AzCore/std/smart_ptr/make_shared.h>
@@ -215,6 +216,13 @@ namespace AzFramework
return clone;
}
AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityTemplate,
EntityIdMap& templateToCloneEntityIdMap, AZ::SerializeContext& serializeContext)
{
return AZ::IdUtils::Remapper<AZ::EntityId>::CloneObjectAndGenerateNewIdsAndFixRefs(
&entityTemplate, templateToCloneEntityIdMap, &serializeContext);
}
bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext)
{
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
@@ -230,48 +238,37 @@ namespace AzFramework
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
size_t entitiesToSpawnSize = entitiesToSpawn.size();
// Map keeps track of ids from template (spawnable) to clone (instance)
// Allowing patch ups of fields referring to entityIds outside of a given entity
EntityIdMap templateToCloneEntityIdMap;
// Reserve buffers
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesToSpawnSize);
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
templateToCloneEntityIdMap.reserve(entitiesToSpawnSize);
// TEMP: To be replaced by IdUtils::Remapper
using EntityIdMap = AZStd::unordered_map<AZ::EntityId, AZ::EntityId>;
EntityIdMap templateToCloneIdMap;
// \TEMP
// Clone the entities from Spawnable
// Mark all indices as spawned
for (size_t i = 0; i < entitiesToSpawnSize; ++i)
{
const AZ::Entity& entityTemplate = *entitiesToSpawn[i];
AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate);
AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, serializeContext);
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
clone->SetId(AZ::Entity::MakeId());
spawnedEntities.push_back(clone);
spawnedEntities.emplace_back(clone);
spawnedEntityIndices.push_back(i);
}
// TEMP: To be replaced by IdUtils::Remapper
templateToCloneIdMap[entityTemplate.GetId()] = clone->GetId();
// Update TransformComponent parent Id. It is guaranteed for the entities array to be sorted from parent->child here.
auto* transformComponent = clone->FindComponent<AzFramework::TransformComponent>();
AZ::EntityId parentId = transformComponent->GetParentId();
if (parentId.IsValid())
{
auto it = templateToCloneIdMap.find(parentId);
if (it != templateToCloneIdMap.end())
{
transformComponent->SetParentRelative(it->second);
}
else
{
AZ_Warning(
"SpawnableEntitiesManager", false, "Entity %s doesn't have the parent entity %s present in the spawnable",
clone->GetName().c_str(), parentId.ToString().data());
}
}
// \TEMP
// loadAll is true if every entity has been spawned only once
if (spawnedEntities.size() == entitiesToSpawnSize)
{
ticket.m_loadAll = true;
}
else
{
// Case where there were already spawns from a previous request
ticket.m_loadAll = false;
}
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
@@ -437,10 +434,23 @@ namespace AzFramework
// to load every, simply start over.
ticket.m_spawnedEntityIndices.clear();
size_t entitiesSize = entities.size();
for (size_t i = 0; i < entitiesSize; ++i)
size_t entitiesToSpawnSize = entities.size();
// Map keeps track of ids from template (spawnable) to clone (instance)
// Allowing patch ups of fields referring to entityIds outside of a given entity
EntityIdMap templateToCloneEntityIdMap;
templateToCloneEntityIdMap.reserve(entitiesToSpawnSize);
// Mark all indices as spawned
for (size_t i = 0; i < entitiesToSpawnSize; ++i)
{
ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[i], serializeContext));
const AZ::Entity& entityTemplate = *entities[i];
AZ::Entity* clone = CloneSingleEntity(entityTemplate, templateToCloneEntityIdMap, serializeContext);
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
ticket.m_spawnedEntities.emplace_back(clone);
ticket.m_spawnedEntityIndices.push_back(i);
}
}
@@ -29,6 +29,8 @@ namespace AZ
namespace AzFramework
{
using EntityIdMap = AZStd::unordered_map<AZ::EntityId, AZ::EntityId>;
class SpawnableEntitiesManager
: public SpawnableEntitiesInterface::Registrar
{
@@ -142,7 +144,11 @@ namespace AzFramework
using Requests = AZStd::variant<SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand,
ListEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, DestroyTicketCommand>;
AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate, AZ::SerializeContext& serializeContext);
AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate,
AZ::SerializeContext& serializeContext);
AZ::Entity* CloneSingleEntity(const AZ::Entity& entityTemplate,
EntityIdMap& templateToCloneEntityIdMap, AZ::SerializeContext& serializeContext);
bool ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext);
bool ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext);
@@ -18,7 +18,6 @@
#include <AzCore/std/numeric.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Windowing/WindowBus.h>
namespace AzFramework
{
@@ -160,24 +159,27 @@ namespace AzFramework
bool CameraSystem::HandleEvents(const InputEvent& event)
{
if (const auto& cursor = AZStd::get_if<CursorEvent>(&event))
if (const auto& horizonalMotion = AZStd::get_if<HorizontalMotionEvent>(&event))
{
m_cursorState.SetCurrentPosition(cursor->m_position);
m_motionDelta.m_x = horizonalMotion->m_delta;
}
else if (const auto& verticalMotion = AZStd::get_if<VerticalMotionEvent>(&event))
{
m_motionDelta.m_y = verticalMotion->m_delta;
}
else if (const auto& scroll = AZStd::get_if<ScrollEvent>(&event))
{
m_scrollDelta = scroll->m_delta;
}
return m_cameras.HandleEvents(event, m_cursorState.CursorDelta(), m_scrollDelta);
return m_cameras.HandleEvents(event, m_motionDelta, m_scrollDelta);
}
Camera CameraSystem::StepCamera(const Camera& targetCamera, const float deltaTime)
{
const auto nextCamera = m_cameras.StepCamera(targetCamera, m_cursorState.CursorDelta(), m_scrollDelta, deltaTime);
m_cursorState.Update();
const auto nextCamera = m_cameras.StepCamera(targetCamera, m_motionDelta, m_scrollDelta, deltaTime);
m_motionDelta = ScreenVector{0, 0};
m_scrollDelta = 0.0f;
return nextCamera;
@@ -193,13 +195,12 @@ namespace AzFramework
bool handling = false;
for (auto& cameraInput : m_activeCameraInputs)
{
cameraInput->HandleEvents(event, cursorDelta, scrollDelta);
handling = !cameraInput->Idle() || handling;
handling = cameraInput->HandleEvents(event, cursorDelta, scrollDelta) || handling;
}
for (auto& cameraInput : m_idleCameraInputs)
{
cameraInput->HandleEvents(event, cursorDelta, scrollDelta);
handling = cameraInput->HandleEvents(event, cursorDelta, scrollDelta) || handling;
}
return handling;
@@ -262,17 +263,26 @@ namespace AzFramework
{
m_activeCameraInputs[i]->Reset();
m_idleCameraInputs.push_back(m_activeCameraInputs[i]);
m_activeCameraInputs[i] = m_activeCameraInputs[m_activeCameraInputs.size() - 1];
using AZStd::swap;
swap(m_activeCameraInputs[i], m_activeCameraInputs[m_activeCameraInputs.size() - 1]);
m_activeCameraInputs.pop_back();
}
}
void Cameras::Clear()
{
Reset();
AZ_Assert(m_activeCameraInputs.empty(), "Active Camera Inputs is not empty");
m_idleCameraInputs.clear();
}
RotateCameraInput::RotateCameraInput(const InputChannelId rotateChannelId)
: m_rotateChannelId(rotateChannelId)
{
}
void RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
bool RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
const ClickDetector::ClickEvent clickEvent = [&event, this] {
if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
@@ -304,6 +314,11 @@ namespace AzFramework
// noop
break;
}
// note - must also check !ending to ensure the mouse up (release) event
// is not consumed and can be propagated to other systems.
// (don't swallow mouse up events)
return !Idle() && !Ending();
}
Camera RotateCameraInput::StepCamera(
@@ -330,7 +345,7 @@ namespace AzFramework
{
}
void PanCameraInput::HandleEvents(
bool PanCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
@@ -347,6 +362,8 @@ namespace AzFramework
}
}
}
return !Idle();
}
Camera PanCameraInput::StepCamera(
@@ -411,7 +428,7 @@ namespace AzFramework
{
}
void TranslateCameraInput::HandleEvents(
bool TranslateCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
@@ -429,7 +446,8 @@ namespace AzFramework
m_boost = true;
}
}
else if (input->m_state == InputChannel::State::Ended)
// ensure we don't process end events in the idle state
else if (input->m_state == InputChannel::State::Ended && !Idle())
{
m_translation &= ~(translationFromKey(input->m_channelId));
if (m_translation == TranslationType::Nil)
@@ -442,6 +460,8 @@ namespace AzFramework
}
}
}
return !Idle();
}
Camera TranslateCameraInput::StepCamera(
@@ -503,7 +523,7 @@ namespace AzFramework
m_boost = false;
}
void OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta)
bool OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta)
{
if (const auto* input = AZStd::get_if<DiscreteInputEvent>(&event))
{
@@ -522,8 +542,10 @@ namespace AzFramework
if (Active())
{
m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta);
return m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta);
}
return !Idle();
}
Camera OrbitCameraInput::StepCamera(
@@ -533,7 +555,7 @@ namespace AzFramework
if (Beginning())
{
const auto hasLookAt = [&nextCamera, &targetCamera, lookAtFn = m_lookAtFn] {
const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn] {
if (lookAtFn)
{
if (const auto lookAt = lookAtFn())
@@ -585,13 +607,15 @@ namespace AzFramework
return nextCamera;
}
void OrbitDollyScrollCameraInput::HandleEvents(
bool OrbitDollyScrollCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto* scroll = AZStd::get_if<ScrollEvent>(&event))
{
BeginActivation();
}
return !Idle();
}
Camera OrbitDollyScrollCameraInput::StepCamera(
@@ -609,7 +633,7 @@ namespace AzFramework
{
}
void OrbitDollyCursorMoveCameraInput::HandleEvents(
bool OrbitDollyCursorMoveCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
@@ -626,6 +650,8 @@ namespace AzFramework
}
}
}
return !Idle();
}
Camera OrbitDollyCursorMoveCameraInput::StepCamera(
@@ -637,13 +663,15 @@ namespace AzFramework
return nextCamera;
}
void ScrollTranslationCameraInput::HandleEvents(
bool ScrollTranslationCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto* scroll = AZStd::get_if<ScrollEvent>(&event))
{
BeginActivation();
}
return !Idle();
}
Camera ScrollTranslationCameraInput::StepCamera(
@@ -694,7 +722,7 @@ namespace AzFramework
return camera;
}
InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize)
InputEvent BuildInputEvent(const InputChannel& inputChannel)
{
const auto& inputChannelId = inputChannel.GetInputChannelId();
const auto& inputDeviceId = inputChannel.GetInputDevice().GetInputDeviceId();
@@ -704,13 +732,13 @@ namespace AzFramework
return button == inputChannelId;
});
if (inputChannelId == InputDeviceMouse::Movement::X || inputChannelId == InputDeviceMouse::Movement::Y)
if (inputChannelId == InputDeviceMouse::Movement::X)
{
const auto* position = inputChannel.GetCustomData<AzFramework::InputChannel::PositionData2D>();
AZ_Assert(position, "Expected PositionData2D but found nullptr");
return CursorEvent{ScreenPoint(
position->m_normalizedPosition.GetX() * windowSize.m_width, position->m_normalizedPosition.GetY() * windowSize.m_height)};
return HorizontalMotionEvent{(int)inputChannel.GetValue()};
}
else if (inputChannelId == InputDeviceMouse::Movement::Y)
{
return VerticalMotionEvent{(int)inputChannel.GetValue()};
}
else if (inputChannelId == InputDeviceMouse::Movement::Z)
{
@@ -18,7 +18,6 @@
#include <AzCore/std/optional.h>
#include <AzFramework/Input/Channels/InputChannel.h>
#include <AzFramework/Viewport/ClickDetector.h>
#include <AzFramework/Viewport/CursorState.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
#include <AzFramework/Viewport/ViewportId.h>
@@ -72,11 +71,16 @@ namespace AzFramework
void UpdateCameraFromTransform(Camera& camera, const AZ::Transform& transform);
struct CursorEvent
//! Generic motion type
template<typename MotionTag>
struct MotionEvent
{
ScreenPoint m_position;
int m_delta;
};
using HorizontalMotionEvent = MotionEvent<struct HorizontalMotionTag>;
using VerticalMotionEvent = MotionEvent<struct VerticalMotionTag>;
struct ScrollEvent
{
float m_delta;
@@ -88,7 +92,7 @@ namespace AzFramework
InputChannel::State m_state; //!< Channel state. (e.g. Begin/update/end event).
};
using InputEvent = AZStd::variant<AZStd::monostate, CursorEvent, ScrollEvent, DiscreteInputEvent>;
using InputEvent = AZStd::variant<AZStd::monostate, HorizontalMotionEvent, VerticalMotionEvent, ScrollEvent, DiscreteInputEvent>;
class CameraInput
{
@@ -149,7 +153,7 @@ namespace AzFramework
ResetImpl();
}
virtual void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) = 0;
virtual bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) = 0;
virtual Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) = 0;
virtual bool Exclusive() const
@@ -171,16 +175,30 @@ namespace AzFramework
class Cameras
{
public:
void AddCamera(AZStd::shared_ptr<CameraInput> cameraInput);
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta);
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime);
void AddCamera(AZStd::shared_ptr<CameraInput> cameraInput);
//! Reset the state of all cameras.
void Reset();
//! Remove all cameras that were added.
void Clear();
//! Is one of the cameras in the active camera inputs marked as 'exclusive'.
//! @note This implies no other sibling cameras can begin while the exclusive camera is running.
bool Exclusive() const;
private:
AZStd::vector<AZStd::shared_ptr<CameraInput>> m_activeCameraInputs;
AZStd::vector<AZStd::shared_ptr<CameraInput>> m_idleCameraInputs;
};
inline bool Cameras::Exclusive() const
{
return AZStd::any_of(
m_activeCameraInputs.begin(), m_activeCameraInputs.end(), [](const auto& cameraInput) { return cameraInput->Exclusive(); });
}
//! Responsible for updating a series of cameras given various inputs.
class CameraSystem
{
public:
@@ -190,8 +208,8 @@ namespace AzFramework
Cameras m_cameras;
private:
CursorState m_cursorState;
float m_scrollDelta = 0.0f;
ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional.
float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional.
};
class RotateCameraInput : public CameraInput
@@ -200,7 +218,7 @@ namespace AzFramework
explicit RotateCameraInput(InputChannelId rotateChannelId);
// CameraInput overrides ...
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
@@ -241,7 +259,7 @@ namespace AzFramework
PanCameraInput(InputChannelId panChannelId, PanAxesFn panAxesFn);
// CameraInput overrides ...
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
@@ -282,7 +300,7 @@ namespace AzFramework
explicit TranslateCameraInput(TranslationAxesFn translationAxesFn);
// CameraInput overrides ...
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
void ResetImpl() override;
@@ -352,7 +370,7 @@ namespace AzFramework
{
public:
// CameraInput overrides ...
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
};
@@ -362,7 +380,7 @@ namespace AzFramework
explicit OrbitDollyCursorMoveCameraInput(InputChannelId dollyChannelId);
// CameraInput overrides ...
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
@@ -373,7 +391,7 @@ namespace AzFramework
{
public:
// CameraInput overrides ...
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
};
@@ -383,7 +401,7 @@ namespace AzFramework
using LookAtFn = AZStd::function<AZStd::optional<AZ::Vector3>()>;
// CameraInput overrides ...
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
bool Exclusive() const override;
@@ -406,8 +424,6 @@ namespace AzFramework
return true;
}
struct WindowSize;
//! Map from a generic InputChannel event to a camera specific InputEvent.
InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize);
InputEvent BuildInputEvent(const InputChannel& inputChannel);
} // namespace AzFramework
@@ -17,6 +17,17 @@ namespace AzFramework
{
ClickDetector::ClickOutcome ClickDetector::DetectClick(const ClickEvent clickEvent, const ScreenVector& cursorDelta)
{
const auto previousDetectionState = m_detectionState;
if (previousDetectionState == DetectionState::WaitingForMove)
{
// only allow the action to begin if the mouse has been moved a small amount
m_moveAccumulator += ScreenVectorLength(cursorDelta);
if (m_moveAccumulator > m_deadZone)
{
m_detectionState = DetectionState::Moved;
}
}
if (clickEvent == ClickEvent::Down)
{
const auto now = std::chrono::steady_clock::now();
@@ -52,15 +63,9 @@ namespace AzFramework
return clickOutcome;
}
if (m_detectionState == DetectionState::WaitingForMove)
if (previousDetectionState == DetectionState::WaitingForMove && m_detectionState == DetectionState::Moved)
{
// only allow the action to begin if the mouse has been moved a small amount
m_moveAccumulator += ScreenVectorLength(cursorDelta);
if (m_moveAccumulator > m_deadZone)
{
m_detectionState = DetectionState::Moved;
return ClickOutcome::Move;
}
return ClickOutcome::Move;
}
return ClickOutcome::Nil;
@@ -50,7 +50,11 @@ namespace AzFramework
//! Called from any type of 'handle event' function.
ClickOutcome DetectClick(ClickEvent clickEvent, const ScreenVector& cursorDelta);
//! Override the default double click interval.
//! @note Default is 400ms - system default.
void SetDoubleClickInterval(float doubleClickInterval);
//! Override the dead zone before a 'move' outcome will be triggered.
void SetDeadZone(float deadZone);
private:
//! Internal state of ClickDetector based on incoming events.
@@ -72,4 +76,9 @@ namespace AzFramework
{
m_doubleClickInterval = doubleClickInterval;
}
inline void ClickDetector::SetDeadZone(const float deadZone)
{
m_deadZone = deadZone;
}
} // namespace AzFramework
@@ -188,6 +188,12 @@ set(FILES
Script/ScriptDebugMsgReflection.h
Script/ScriptRemoteDebugging.cpp
Script/ScriptRemoteDebugging.h
Session/ISessionHandlingRequests.h
Session/ISessionRequests.cpp
Session/ISessionRequests.h
Session/SessionConfig.cpp
Session/SessionConfig.h
Session/SessionNotifications.h
StreamingInstall/StreamingInstall.h
StreamingInstall/StreamingInstall.cpp
StreamingInstall/StreamingInstallRequests.h
@@ -19,6 +19,8 @@
namespace AzNetworking
{
using NetworkInterfaces = AZStd::unordered_map<AZ::Name, AZStd::unique_ptr<INetworkInterface>>;
//! @class INetworking
//! @brief The interface for creating and working with network interfaces.
class INetworking
@@ -60,5 +62,25 @@ namespace AzNetworking
//! @param name The name of the Compressor factory to unregister, must match result of factory->GetFactoryName()
//! @return Whether the factory was found and unregistered
virtual bool UnregisterCompressorFactory(AZ::Name name) = 0;
//! Returns the raw network interfaces owned by the networking instance.
//! @return the raw network interfaces owned by the networking instance
virtual const NetworkInterfaces& GetNetworkInterfaces() const = 0;
//! Returns the number of sockets monitored by our TcpListenThread.
//! @return the number of sockets monitored by our TcpListenThread
virtual uint32_t GetTcpListenThreadSocketCount() const = 0;
//! Returns the total time spent updating our TcpListenThread.
//! @return the total time spent updating our TcpListenThread
virtual AZ::TimeMs GetTcpListenThreadUpdateTime() const = 0;
//! Returns the number of sockets monitored by our UdpReaderThread.
//! @return the number of sockets monitored by our UdpReaderThread
virtual uint32_t GetUdpReaderThreadSocketCount() const = 0;
//! Returns the total time spent updating our UdpReaderThread.
//! @return the total time spent updating our UdpReaderThread
virtual AZ::TimeMs GetUdpReaderThreadUpdateTime() const = 0;
};
}
@@ -149,12 +149,37 @@ namespace AzNetworking
return m_compressorFactories.erase(name) > 0;
}
const NetworkInterfaces& NetworkingSystemComponent::GetNetworkInterfaces() const
{
return m_networkInterfaces;
}
uint32_t NetworkingSystemComponent::GetTcpListenThreadSocketCount() const
{
return m_listenThread->GetSocketCount();
}
AZ::TimeMs NetworkingSystemComponent::GetTcpListenThreadUpdateTime() const
{
return m_listenThread->GetUpdateTimeMs();
}
uint32_t NetworkingSystemComponent::GetUdpReaderThreadSocketCount() const
{
return m_readerThread->GetSocketCount();
}
AZ::TimeMs NetworkingSystemComponent::GetUdpReaderThreadUpdateTime() const
{
return m_readerThread->GetUpdateTimeMs();
}
void NetworkingSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{
AZLOG_INFO("Total sockets monitored by TcpListenThread: %u", m_listenThread->GetSocketCount());
AZLOG_INFO("Total time spent updating TcpListenThread: %lld", aznumeric_cast<AZ::s64>(m_listenThread->GetUpdateTimeMs()));
AZLOG_INFO("Total sockets monitored by UdpReaderThread: %u", m_readerThread->GetSocketCount());
AZLOG_INFO("Total time spent updating UdpReaderThread: %lld", aznumeric_cast<AZ::s64>(m_readerThread->GetUpdateTimeMs()));
AZLOG_INFO("Total sockets monitored by TcpListenThread: %u", GetTcpListenThreadSocketCount());
AZLOG_INFO("Total time spent updating TcpListenThread: %lld", aznumeric_cast<AZ::s64>(GetTcpListenThreadUpdateTime()));
AZLOG_INFO("Total sockets monitored by UdpReaderThread: %u", GetUdpReaderThreadSocketCount());
AZLOG_INFO("Total time spent updating UdpReaderThread: %lld", aznumeric_cast<AZ::s64>(GetUdpReaderThreadUpdateTime()));
for (auto& networkInterface : m_networkInterfaces)
{
@@ -63,6 +63,11 @@ namespace AzNetworking
void RegisterCompressorFactory(ICompressorFactory* factory) override;
AZStd::unique_ptr<ICompressor> CreateCompressor(AZ::Name name) override;
bool UnregisterCompressorFactory(AZ::Name name) override;
const NetworkInterfaces& GetNetworkInterfaces() const override;
uint32_t GetTcpListenThreadSocketCount() const override;
AZ::TimeMs GetTcpListenThreadUpdateTime() const override;
uint32_t GetUdpReaderThreadSocketCount() const override;
AZ::TimeMs GetUdpReaderThreadUpdateTime() const override;
//! @}
//! Console commands.
@@ -74,7 +79,6 @@ namespace AzNetworking
AZ_CONSOLEFUNC(NetworkingSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for all instantiated network interfaces");
using NetworkInterfaces = AZStd::unordered_map<AZ::Name, AZStd::unique_ptr<INetworkInterface>>;
NetworkInterfaces m_networkInterfaces;
AZStd::unique_ptr<TcpListenThread> m_listenThread;
AZStd::unique_ptr<UdpReaderThread> m_readerThread;
@@ -46,6 +46,12 @@ namespace AzNetworking
void TcpSocketManager::ProcessEvents(AZ::TimeMs maxBlockMs, const SocketEventCallback& readCallback, const SocketEventCallback& writeCallback)
{
if(static_cast<int32_t>(m_maxFd) <= 0 && m_socketFds.empty())
{
// There are no available sockets to process
return;
}
m_readerFdSet = m_sourceFdSet;
m_writerFdSet = m_sourceFdSet;
@@ -52,6 +52,21 @@ namespace AzToolsFramework
* Deletes all entities in the provided list, as well as their transform descendants.
*/
virtual void DeleteEntitiesAndAllDescendants(const EntityIdList& entities) = 0;
/**
* Duplicate all currently-selected entities.
*/
virtual void DuplicateSelected() = 0;
/**
* Duplicates the specified entity.
*/
virtual void DuplicateEntityById(AZ::EntityId entityId) = 0;
/**
* Duplicates all specified entities.
*/
virtual void DuplicateEntities(const EntityIdList& entities) = 0;
};
} // namespace AzToolsFramework
@@ -43,7 +43,7 @@ namespace AzToolsFramework
void EditorEntityManager::DeleteEntityById(AZ::EntityId entityId)
{
DeleteEntities({entityId});
DeleteEntities(EntityIdList{ entityId });
}
void EditorEntityManager::DeleteEntities(const EntityIdList& entities)
@@ -53,12 +53,30 @@ namespace AzToolsFramework
void EditorEntityManager::DeleteEntityAndAllDescendants(AZ::EntityId entityId)
{
DeleteEntitiesAndAllDescendants({entityId});
DeleteEntitiesAndAllDescendants(EntityIdList{ entityId });
}
void EditorEntityManager::DeleteEntitiesAndAllDescendants(const EntityIdList& entities)
{
m_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(entities);
}
void EditorEntityManager::DuplicateSelected()
{
EntityIdList selectedEntities;
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
m_prefabPublicInterface->DuplicateEntitiesInInstance(selectedEntities);
}
void EditorEntityManager::DuplicateEntityById(AZ::EntityId entityId)
{
DuplicateEntities(EntityIdList{ entityId });
}
void EditorEntityManager::DuplicateEntities(const EntityIdList& entities)
{
m_prefabPublicInterface->DuplicateEntitiesInInstance(entities);
}
}
@@ -31,6 +31,9 @@ namespace AzToolsFramework
void DeleteEntities(const EntityIdList& entities) override;
void DeleteEntityAndAllDescendants(AZ::EntityId entityId) override;
void DeleteEntitiesAndAllDescendants(const EntityIdList& entities) override;
void DuplicateSelected() override;
void DuplicateEntityById(AZ::EntityId entityId) override;
void DuplicateEntities(const EntityIdList& entities) override;
private:
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
@@ -28,6 +28,7 @@ namespace AzToolsFramework
inline static const char* PatchesName = "Patches";
inline static const char* SourceName = "Source";
inline static const char* LinkIdName = "LinkId";
inline static const char* EntityIdName = "Id";
inline static const char* EntitiesName = "Entities";
inline static const char* ContainerEntityName = "ContainerEntity";
@@ -13,6 +13,8 @@
#include <AzToolsFramework/Prefab/PrefabPublicHandler.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/JSON/stringbuffer.h>
#include <AzCore/JSON/writer.h>
#include <AzCore/Utils/TypeHash.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
@@ -31,6 +33,8 @@
#include <AzToolsFramework/Prefab/PrefabUndoHelpers.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <QString>
namespace AzToolsFramework
{
namespace Prefab
@@ -83,7 +87,9 @@ namespace AzToolsFramework
commonRootInstanceDomBeforeCreate, commonRootEntityOwningInstance->get());
AZStd::vector<AZ::Entity*> entities;
AZStd::vector<AZStd::unique_ptr<Instance>> instances;
AZStd::vector<AZStd::unique_ptr<Instance>> instancePtrs;
AZStd::vector<Instance*> instances;
AZStd::unordered_map<Instance*, PrefabDom> nestedInstanceLinkPatchesMap;
// Retrieve all entities affected and identify Instances
if (!RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonRootEntityOwningInstance->get(), entities, instances))
@@ -92,11 +98,31 @@ namespace AzToolsFramework
AZStd::string("Could not create a new prefab out of the entities provided - invalid selection."));
}
// Detach the retrieved entities
for (AZ::Entity* entity : entities)
{
commonRootEntityOwningInstance->get().DetachEntity(entity->GetId()).release();
}
// When we create a prefab with other prefab instances, we have to remove the existing links between the source and
// target templates of the other instances.
for (auto& nestedInstance : instances)
{
RemoveLink(nestedInstance, commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
AZStd::unique_ptr<Instance> outInstance = commonRootEntityOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstance->GetLinkId());
if (linkRef.has_value())
{
PrefabDom oldLinkPatches;
oldLinkPatches.CopyFrom(linkRef->get().GetLinkDom(), oldLinkPatches.GetAllocator());
nestedInstanceLinkPatchesMap.emplace(nestedInstance, AZStd::move(oldLinkPatches));
}
RemoveLink(outInstance, commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
instancePtrs.emplace_back(AZStd::move(outInstance));
}
PrefabUndoHelpers::UpdatePrefabInstance(
@@ -112,7 +138,7 @@ namespace AzToolsFramework
// Create the Prefab
instanceToCreate = prefabEditorEntityOwnershipInterface->CreatePrefab(
entities, AZStd::move(instances), filePath, commonRootEntityOwningInstance);
entities, AZStd::move(instancePtrs), filePath, commonRootEntityOwningInstance);
if (!instanceToCreate)
{
@@ -122,6 +148,9 @@ namespace AzToolsFramework
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
// Apply the correct transform to the container for the new instance, and store the patch for use when creating the link.
PrefabDom patch = ApplyContainerTransformAndGeneratePatch(containerEntityId, commonRootEntityId, topLevelEntities);
// Parent the non-container top level entities to the container entity.
// Parenting the top level container entities will be done during the creation of links.
for (AZ::Entity* topLevelEntity : topLevelEntities)
@@ -141,35 +170,55 @@ namespace AzToolsFramework
instanceToCreate->get().GetNestedInstances([&](AZStd::unique_ptr<Instance>& nestedInstance) {
AZ_Assert(nestedInstance, "Invalid nested instance found in the new prefab created.");
EntityOptionalReference nestedInstanceContainerEntity = nestedInstance->GetContainerEntity();
AZ_Assert(
nestedInstanceContainerEntity, "Invalid container entity found for the nested instance used in prefab creation.");
AZ::EntityId parentId;
AZ::TransformBus::EventResult(
parentId, nestedInstanceContainerEntity->get().GetId(), &AZ::TransformBus::Events::GetParentId);
AZ::EntityId nestedInstanceContainerEntityId = nestedInstanceContainerEntity->get().GetId();
PrefabDom previousPatch;
auto entityIterator = AZStd::find_if(
entities.begin(), entities.end(), [parentId](AZ::Entity* entity) { return entity->GetId() == parentId; });
// If the previous parent entity of the nested instance is not part of the entities of the newly created prefab,
// then set the parent of the nested prefab as the container entity of the newly created prefab.
if (entityIterator == entities.end())
// Retrieve the previous patch if it exists
if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get()))
{
parentId = containerEntityId;
previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]);
}
// These link creations shouldn't be undone because that would put the template in a non-usable state if a user
// chooses to instantiate the template after undoing the creation.
CreateLink(
{&nestedInstanceContainerEntity->get()}, *nestedInstance, instanceToCreate->get().GetTemplateId(),
undoBatch.GetUndoBatch(), parentId, false);
CreateLink(*nestedInstance, instanceToCreate->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(previousPatch), false);
// If this nested instance's container is a top level entity in the new prefab, re-parent it and apply the change.
if (AZStd::find(topLevelEntities.begin(), topLevelEntities.end(), &nestedInstanceContainerEntity->get()) != topLevelEntities.end())
{
Prefab::PrefabDom containerEntityDomBefore;
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *nestedInstanceContainerEntity);
AZ::TransformBus::Event(nestedInstanceContainerEntityId, &AZ::TransformBus::Events::SetParent, containerEntityId);
PrefabDom containerEntityDomAfter;
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *nestedInstanceContainerEntity);
PrefabDom reparentPatch;
m_instanceToTemplateInterface->GeneratePatch(reparentPatch, containerEntityDomBefore, containerEntityDomAfter);
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(reparentPatch, nestedInstanceContainerEntityId);
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes as a separate step
m_prefabUndoCache.Store(nestedInstanceContainerEntityId, AZStd::move(containerEntityDomAfter));
// Save these changes as patches to the link
PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(nestedInstanceContainerEntityId)));
linkUpdate->SetParent(undoBatch.GetUndoBatch());
linkUpdate->Capture(reparentPatch, nestedInstance->GetLinkId());
linkUpdate->Redo();
}
});
// Create a link between the templates of the newly created instance and the instance it's being parented under.
CreateLink(
topLevelEntities, instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(),
undoBatch.GetUndoBatch(), commonRootEntityId);
instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(),
AZStd::move(patch));
for (AZ::Entity* topLevelEntity : topLevelEntities)
{
@@ -199,6 +248,40 @@ namespace AzToolsFramework
return AZ::Success();
}
PrefabDom PrefabPublicHandler::ApplyContainerTransformAndGeneratePatch(AZ::EntityId containerEntityId, AZ::EntityId parentEntityId, const EntityList& childEntities)
{
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
AZ_Assert(containerEntity, "Invalid container entity passed to ApplyContainerTransformAndGeneratePatch.");
// Generate the transform for the container entity out of the top level entities, and set it
// This step needs to be done before anything is parented to the container, else children position will be wrong
Prefab::PrefabDom containerEntityDomBefore;
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity);
AZ::Vector3 containerEntityTranslation(AZ::Vector3::CreateZero());
AZ::Quaternion containerEntityRotation(AZ::Quaternion::CreateZero());
// Set container entity to be child of common root
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, parentEntityId);
// Set the transform (translation, rotation) of the container entity
GenerateContainerEntityTransform(childEntities, containerEntityTranslation, containerEntityRotation);
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTranslation, containerEntityTranslation);
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalRotationQuaternion, containerEntityRotation);
PrefabDom containerEntityDomAfter;
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity);
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter);
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
return AZStd::move(patch);
}
PrefabOperationResult PrefabPublicHandler::InstantiatePrefab(
AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position)
{
@@ -249,10 +332,10 @@ namespace AzToolsFramework
// Initialize Undo Batch object
ScopedUndoBatch undoBatch("Instantiate Prefab");
// Instantiate the Prefab
PrefabDom instanceToParentUnderDomBeforeCreate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceToParentUnderDomBeforeCreate, instanceToParentUnder->get());
// Instantiate the Prefab
auto instanceToCreate = prefabEditorEntityOwnershipInterface->InstantiatePrefab(relativePath, instanceToParentUnder);
if (!instanceToCreate)
@@ -264,11 +347,32 @@ namespace AzToolsFramework
PrefabUndoHelpers::UpdatePrefabInstance(
instanceToParentUnder->get(), "Update prefab instance", instanceToParentUnderDomBeforeCreate, undoBatch.GetUndoBatch());
CreateLink({}, instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), parent);
// Create Link with correct container patches
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
AZ_Assert(containerEntity, "Invalid container entity detected in InstantiatePrefab.");
// Apply position
Prefab::PrefabDom containerEntityDomBefore;
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity);
// Set container entity's parent
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, parent);
// Set the position of the container entity
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetWorldTranslation, position);
PrefabDom containerEntityDomAfter;
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity);
// Generate patch to be stored in the link
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter);
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
CreateLink(instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(patch));
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
}
return AZ::Success();
@@ -299,7 +403,7 @@ namespace AzToolsFramework
// Find common root and top level entities
bool entitiesHaveCommonRoot = false;
AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
entitiesHaveCommonRoot, &AzToolsFramework::ToolsApplicationRequests::FindCommonRootInactive, inputEntityList,
commonRootEntityId, &topLevelEntities);
@@ -335,33 +439,9 @@ namespace AzToolsFramework
}
void PrefabPublicHandler::CreateLink(
const EntityList& topLevelEntities, Instance& sourceInstance, TemplateId targetTemplateId,
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId commonRootEntityId, const bool isUndoRedoSupportNeeded)
Instance& sourceInstance, TemplateId targetTemplateId,
UndoSystem::URSequencePoint* undoBatch, PrefabDom patch, const bool isUndoRedoSupportNeeded)
{
AZ::EntityId containerEntityId = sourceInstance.GetContainerEntityId();
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
Prefab::PrefabDom containerEntityDomBefore;
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity);
AZ::Vector3 containerEntityTranslation(AZ::Vector3::CreateZero());
AZ::Quaternion containerEntityRotation(AZ::Quaternion::CreateZero());
// Set the transform (translation, rotation) of the container entity
GenerateContainerEntityTransform(topLevelEntities, containerEntityTranslation, containerEntityRotation);
// Set container entity to be child of common root
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, commonRootEntityId);
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTranslation, containerEntityTranslation);
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalRotationQuaternion, containerEntityRotation);
PrefabDom containerEntityDomAfter;
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity);
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter);
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
LinkId linkId;
if (isUndoRedoSupportNeeded)
{
@@ -377,9 +457,6 @@ namespace AzToolsFramework
}
sourceInstance.SetLinkId(linkId);
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
}
void PrefabPublicHandler::RemoveLink(
@@ -648,6 +725,151 @@ namespace AzToolsFramework
return DeleteFromInstance(entityIds, true);
}
PrefabOperationResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
{
if (entityIds.empty())
{
return AZ::Failure(AZStd::string("No entities to duplicate."));
}
if (!EntitiesBelongToSameInstance(entityIds))
{
return AZ::Failure(AZStd::string("Cannot duplicate multiple "
"entities belonging to different instances with one operation."));
}
// We've already verified the entities are all owned by the same instance,
// so we can just retrieve our instance from the first entity in the list.
InstanceOptionalReference commonEntityOwningInstance = GetOwnerInstanceByEntityId(entityIds[0]);
AZ_Assert(
commonEntityOwningInstance.has_value(),
"Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided");
// This will cull out any entities that have ancestors in the list, since we will end up duplicating
// the full nested hierarchy with what is returned from RetrieveAndSortPrefabEntitiesAndInstances
AzToolsFramework::EntityIdSet duplicationSet = AzToolsFramework::GetCulledEntityHierarchy(entityIds);
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
ScopedUndoBatch undoBatch("Duplicate Entities");
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities");
// Take a snapshot of the instance DOM before we manipulate it
Prefab::PrefabDom instanceDomBefore;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonEntityOwningInstance->get());
AZStd::vector<AZ::Entity*> entities;
AZStd::vector<Instance*> instances;
// Gather all entities/instances in the hierarchy, but don't detach them because we are duplicating not deleting.
EntityList inputEntityList = EntityIdSetToEntityList(duplicationSet);
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonEntityOwningInstance->get(), entities, instances);
if (!success)
{
return AZ::Failure(AZStd::string("Failed to retrieve entities and instances from the given list of entity ids for duplication"));
}
// Make a copy of our before instance DOM where we will add our duplicated entities
Prefab::PrefabDom instanceDomAfter;
instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator());
AZStd::unordered_map<EntityAlias, EntityAlias> oldAliasToNewAliasMap;
AZStd::unordered_map<EntityAlias, QString> aliasToEntityDomMap;
for (AZ::Entity* entity : entities)
{
EntityAliasOptionalReference oldAliasRef = commonEntityOwningInstance->get().GetEntityAlias(entity->GetId());
AZ_Assert(oldAliasRef.has_value(), "No alias found for Entity in the DOM");
EntityAlias oldAlias = oldAliasRef.value();
// Give this the outer allocator so that the memory reference will be valid when
// it gets used for AddMember
Prefab::PrefabDom entityDomBefore(&instanceDomAfter.GetAllocator());
m_instanceToTemplateInterface->GenerateDomForEntity(entityDomBefore, *entity);
// Keep track of the old alias <-> new alias mapping for this duplicated entity
// so we can fixup references later
EntityAlias newEntityAlias = Instance::GenerateEntityAlias();
oldAliasToNewAliasMap.insert(AZStd::make_pair(oldAlias, newEntityAlias));
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
entityDomBefore.Accept(writer);
// Store our duplicated Entity DOM with its new alias as a string
// so that we can fixup entity alias references before adding it
// to the Entities member of our instance DOM
QString entityDomString(buffer.GetString());
aliasToEntityDomMap.insert(AZStd::make_pair(newEntityAlias, entityDomString));
}
auto entitiesIter = instanceDomAfter.FindMember(PrefabDomUtils::EntitiesName);
AZ_Assert(entitiesIter != instanceDomAfter.MemberEnd(), "Instance DOM missing the Entities member.");
// Now that all the duplicated Entity DOMs have been created, we need to iterate
// through them and replace any previous EntityAlias references with the new ones.
// These are more than just parent entity references for nested entities, this will
// also cover any EntityId references that were made in the components between them.
for (auto aliasEntityPair : aliasToEntityDomMap)
{
EntityAlias newEntityAlias = aliasEntityPair.first;
QString newEntityDomString = aliasEntityPair.second;
// Replace all of the old alias references with the new ones
// We bookend the aliases with \" and also with a / as an extra precaution to prevent
// inadvertently replacing a matching string vs. where an actual EntityId is expected
// This will cover both cases where an alias could be used in a normal entity vs. an instance
for (auto aliasMapIter : oldAliasToNewAliasMap)
{
QString oldAliasQuotes = QString("\"%1\"").arg(aliasMapIter.first.c_str());
QString newAliasQuotes = QString("\"%1\"").arg(aliasMapIter.second.c_str());
newEntityDomString.replace(oldAliasQuotes, newAliasQuotes);
QString oldAliasPathRef = QString("/%1").arg(aliasMapIter.first.c_str());
QString newAliasPathRef = QString("/%1").arg(aliasMapIter.second.c_str());
newEntityDomString.replace(oldAliasPathRef, newAliasPathRef);
}
// Create the new Entity DOM from parsing the JSON string
Prefab::PrefabDom entityDomAfter(&instanceDomAfter.GetAllocator());
entityDomAfter.Parse(newEntityDomString.toUtf8().constData());
// Add the new Entity DOM to the Entities member of the instance
rapidjson::Value aliasName(newEntityAlias.c_str(), newEntityAlias.length(), instanceDomAfter.GetAllocator());
entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, instanceDomAfter.GetAllocator());
}
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity duplication");
command->SetParent(undoBatch.GetUndoBatch());
command->Capture(instanceDomBefore, instanceDomAfter, commonEntityOwningInstance->get().GetTemplateId());
command->RunRedo();
EntityIdList duplicatedEntityIds;
for (auto aliasMapIter : oldAliasToNewAliasMap)
{
EntityAlias newEntityAlias = aliasMapIter.second;
AliasPath absoluteEntityPath = commonEntityOwningInstance->get().GetAbsoluteInstanceAliasPath();
absoluteEntityPath.Append(newEntityAlias);
AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath);
duplicatedEntityIds.push_back(newEntityId);
}
// Select the duplicated entities
auto selectionUndo = aznew SelectionCommand(duplicatedEntityIds, "Select Duplicated Entities");
selectionUndo->SetParent(undoBatch.GetUndoBatch());
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::RunRedoSeparately, selectionUndo);
}
return AZ::Success();
}
PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
{
if (entityIds.empty())
@@ -675,17 +897,7 @@ namespace AzToolsFramework
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.");
}
ScopedUndoBatch undoBatch("Delete Selected");
// In order to undo DeleteSelected, we have to create a selection command which selects the current selection
// and then add the deletion as children.
@@ -713,7 +925,7 @@ namespace AzToolsFramework
if (deleteDescendants)
{
AZStd::vector<AZ::Entity*> entities;
AZStd::vector<AZStd::unique_ptr<Instance>> instances;
AZStd::vector<Instance*> instances;
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
@@ -724,13 +936,15 @@ namespace AzToolsFramework
for (AZ::Entity* entity : entities)
{
commonOwningInstance->get().DetachEntity(entity->GetId()).release();
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entity->GetId());
}
for (auto& nestedInstance : instances)
{
RemoveLink(nestedInstance, commonOwningInstance->get().GetTemplateId(), currentUndoBatch);
nestedInstance.reset();
AZStd::unique_ptr<Instance> outInstance = commonOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
RemoveLink(outInstance, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
outInstance.reset();
}
}
else
@@ -742,7 +956,7 @@ namespace AzToolsFramework
if (owningInstance->get().GetContainerEntityId() == entityId)
{
auto instancePtr = commonOwningInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), currentUndoBatch);
RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
}
else
{
@@ -760,17 +974,12 @@ namespace AzToolsFramework
command->SetParent(selCommand);
}
selCommand->SetParent(currentUndoBatch);
selCommand->SetParent(undoBatch.GetUndoBatch());
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DeleteEntities:RunRedo");
selCommand->RunRedo();
}
if (createdUndo)
{
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::EndUndoBatch);
}
return AZ::Success();
}
@@ -882,7 +1091,7 @@ namespace AzToolsFramework
bool PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances(
const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
EntityList& outEntities, AZStd::vector<AZStd::unique_ptr<Instance>>& outInstances) const
EntityList& outEntities, AZStd::vector<Instance*>& outInstances) const
{
if (inputEntities.size() == 0)
{
@@ -966,14 +1175,14 @@ namespace AzToolsFramework
for (AZ::Entity* entity : entities)
{
outEntities.emplace_back(commonRootEntityOwningInstance.DetachEntity(entity->GetId()).release());
outEntities.emplace_back(entity);
}
outInstances.clear();
outInstances.reserve(instances.size());
for (Instance* instancePtr : instances)
{
outInstances.push_back(AZStd::move(commonRootEntityOwningInstance.DetachNestedInstance(instancePtr->GetInstanceAlias())));
outInstances.push_back(instancePtr);
}
return (outEntities.size() + outInstances.size()) > 0;
@@ -60,28 +60,41 @@ namespace AzToolsFramework
PrefabOperationResult DeleteEntitiesInInstance(const EntityIdList& entityIds) override;
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
private:
PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants);
bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
EntityList& outEntities, AZStd::vector<AZStd::unique_ptr<Instance>>& outInstances) const;
EntityList& outEntities, AZStd::vector<Instance*>& outInstances) const;
InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const;
bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const;
/**
* Applies the correct transform changes to the container entity based on the parent and child entities provided, and returns an appropriate patch.
* The container will be parented to parentId, moved to the average transform of the future direct children and its cache will be updated.
* This helper function won't support undo/redo, update the templates or create any links. All that needs to be done by the caller.
*
* \param containerEntityId The container to apply the changes to.
* \param parentEntityId The id of the entity the container should be parented to.
* \param childEntities A list of entities that will subsequently be parented to this container.
* \return The PrefabDom containing the patches that should be stored in the parent link.
*/
PrefabDom ApplyContainerTransformAndGeneratePatch(
AZ::EntityId containerEntityId, AZ::EntityId parentEntityId, const EntityList& childEntities);
/**
* Creates a link between the templates of an instance and its parent.
*
* \param topLevelEntities The list of entities that are immediate children to the container entity of the instance.
* \param sourceInstance The instance that corresponds to the source template of the link.
* \param targetInstance The id of the target template.
* \param undoBatch The undo batch to set as parent for this create link action.
* \param commonRootEntityId The id of the entity that the source instance should be parented under.
* \param patch The patch to store in the newly created link dom.
* \param isUndoRedoSupportNeeded The flag indicating whether the link should be created with undo/redo support or not.
*/
void CreateLink(
const EntityList& topLevelEntities, Instance& sourceInstance, TemplateId targetTemplateId,
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId commonRootEntityId, const bool isUndoRedoSupportNeeded = true);
Instance& sourceInstance, TemplateId targetTemplateId, UndoSystem::URSequencePoint* undoBatch,
PrefabDom patch, const bool isUndoRedoSupportNeeded = true);
/**
* Removes the link between template of the sourceInstance and the template corresponding to targetTemplateId.
@@ -143,6 +143,13 @@ namespace AzToolsFramework
* @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;
/**
* Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance.
* @param entities The entities to duplicate.
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
};
} // namespace Prefab
@@ -63,6 +63,7 @@
#include <AzToolsFramework/UI/Outliner/EntityOutlinerDisplayOptionsMenu.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerSortFilterProxyModel.hxx>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerCacheBus.h>
#include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
////////////////////////////////////////////////////////////////////////////
@@ -1409,6 +1410,16 @@ namespace AzToolsFramework
{
(void)name;
QueueEntityUpdate(entityId);
bool isSelected = false;
AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
isSelected, &AzToolsFramework::ToolsApplicationRequests::IsSelected, entityId);
if (isSelected)
{
// Ask the system to scroll to the entity in case it is off screen after the rename
EntityOutlinerModelNotificationBus::Broadcast(&EntityOutlinerModelNotifications::QueueScrollToNewContent, entityId);
}
}
void EntityOutlinerListModel::OnEntityInfoUpdatedUnsavedChanges(AZ::EntityId entityId)
@@ -93,28 +93,12 @@ namespace AzToolsFramework
EditorContextMenuBus::Handler::BusConnect();
PrefabInstanceContainerNotificationBus::Handler::BusConnect();
AZ::Interface<PrefabIntegrationInterface>::Register(this);
bool prefabWipFeaturesEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
if (prefabWipFeaturesEnabled)
{
AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension);
}
AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension);
}
PrefabIntegrationManager::~PrefabIntegrationManager()
{
bool prefabWipFeaturesEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
if (prefabWipFeaturesEnabled)
{
AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect();
}
AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect();
AZ::Interface<PrefabIntegrationInterface>::Unregister(this);
PrefabInstanceContainerNotificationBus::Handler::BusDisconnect();
EditorContextMenuBus::Handler::BusDisconnect();
@@ -137,66 +121,63 @@ namespace AzToolsFramework
void PrefabIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu) const
{
bool prefabWipFeaturesEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
AzToolsFramework::EntityIdList selectedEntities;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
selectedEntities, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
if (prefabWipFeaturesEnabled)
bool prefabWipFeaturesEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
// Create Prefab
{
// Create Prefab
if (!selectedEntities.empty())
{
if (!selectedEntities.empty())
// Hide if the only selected entity is the Level Container
if (selectedEntities.size() > 1 || !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntities[0]))
{
// Hide if the only selected entity is the Level Container
if (selectedEntities.size() > 1 || !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntities[0]))
bool layerInSelection = false;
for (AZ::EntityId entityId : selectedEntities)
{
bool layerInSelection = false;
for (AZ::EntityId entityId : selectedEntities)
{
if (!layerInSelection)
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerInSelection, entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
if (layerInSelection)
{
break;
}
}
}
// Layers can't be in prefabs.
if (!layerInSelection)
{
QAction* createAction = menu->addAction(QObject::tr("Create Prefab..."));
createAction->setToolTip(QObject::tr("Creates a prefab out of the currently selected entities."));
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerInSelection, entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
QObject::connect(createAction, &QAction::triggered, createAction, [this, selectedEntities] {
ContextMenu_CreatePrefab(selectedEntities);
});
if (layerInSelection)
{
break;
}
}
}
// Layers can't be in prefabs.
if (!layerInSelection)
{
QAction* createAction = menu->addAction(QObject::tr("Create Prefab..."));
createAction->setToolTip(QObject::tr("Creates a prefab out of the currently selected entities."));
QObject::connect(createAction, &QAction::triggered, createAction, [this, selectedEntities] {
ContextMenu_CreatePrefab(selectedEntities);
});
}
}
}
// Instantiate Prefab
{
QAction* instantiateAction = menu->addAction(QObject::tr("Instantiate Prefab..."));
instantiateAction->setToolTip(QObject::tr("Instantiates a prefab file in the scene."));
QObject::connect(
instantiateAction, &QAction::triggered, instantiateAction, [this] { ContextMenu_InstantiatePrefab(); });
}
menu->addSeparator();
}
// Instantiate Prefab
{
QAction* instantiateAction = menu->addAction(QObject::tr("Instantiate Prefab..."));
instantiateAction->setToolTip(QObject::tr("Instantiates a prefab file in the scene."));
QObject::connect(
instantiateAction, &QAction::triggered, instantiateAction, [this] { ContextMenu_InstantiatePrefab(); });
}
menu->addSeparator();
bool itemWasShown = false;
// Edit/Save Prefab
@@ -27,6 +27,7 @@ AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") // 4244: con
#include <QtGui/QTextLayout>
#include <QtGui/QPainter>
#include <QMessageBox>
#include <QStylePainter>
AZ_POP_DISABLE_WARNING
static const int LabelColumnStretch = 2;
@@ -121,6 +122,19 @@ namespace AzToolsFramework
setLayout(m_mainLayout);
}
void PropertyRowWidget::paintEvent(QPaintEvent* event)
{
QStylePainter p(this);
if (CanBeReordered())
{
const QPen linePen(QColor(0x3B3E3F));
p.setPen(linePen);
int indent = m_treeDepth * m_treeIndentation;
p.drawLine(event->rect().topLeft() + QPoint(indent, 0), event->rect().topRight());
}
}
bool PropertyRowWidget::HasChildWidgetAlready() const
{
return m_childWidget != nullptr;
@@ -1661,6 +1675,20 @@ namespace AzToolsFramework
m_nameLabel->setFilter(m_currentFilterString);
}
bool PropertyRowWidget::CanChildrenBeReordered() const
{
return m_containerEditable;
}
bool PropertyRowWidget::CanBeReordered() const
{
if (!m_parentRow)
{
return false;
}
return m_parentRow->CanChildrenBeReordered();
}
}
#include "UI/PropertyEditor/moc_PropertyRowWidget.cpp"
@@ -44,6 +44,7 @@ namespace AzToolsFramework
Q_PROPERTY(bool hasChildRows READ HasChildRows);
Q_PROPERTY(bool isTopLevel READ IsTopLevel);
Q_PROPERTY(int getLevel READ GetLevel);
Q_PROPERTY(bool canBeReordered READ CanBeReordered);
Q_PROPERTY(bool appendDefaultLabelToName READ GetAppendDefaultLabelToName WRITE AppendDefaultLabelToName)
public:
AZ_CLASS_ALLOCATOR(PropertyRowWidget, AZ::SystemAllocator, 0)
@@ -126,6 +127,7 @@ namespace AzToolsFramework
void SetSelectionEnabled(bool selectionEnabled);
void SetSelected(bool selected);
bool eventFilter(QObject *watched, QEvent *event) override;
void paintEvent(QPaintEvent*) override;
/// Apply tooltip to widget and some of its children.
void SetDescription(const QString& text);
@@ -146,6 +148,9 @@ namespace AzToolsFramework
QLabel* GetNameLabel() { return m_nameLabel; }
void SetIndentSize(int w);
void SetAsCustom(bool custom) { m_custom = custom; }
bool CanChildrenBeReordered() const;
bool CanBeReordered() const;
protected:
int CalculateLabelWidth() const;
@@ -138,7 +138,7 @@ namespace UnitTest
if (!GetApplication())
{
// Create & Start a new ToolsApplication if there's no existing one
m_app = AZStd::make_unique<ToolsTestApplication>("ToolsApplication");
m_app = CreateTestApplication();
m_app->Start(AzFramework::Application::Descriptor());
}
@@ -216,6 +216,12 @@ namespace UnitTest
TestEditorActions m_editorActions;
ToolsApplicationMessageHandler m_messageHandler; // used to suppress trace messages in test output
// Override this if your test fixture needs to use a custom TestApplication
virtual AZStd::unique_ptr<ToolsTestApplication> CreateTestApplication()
{
return AZStd::make_unique<ToolsTestApplication>("ToolsApplication");
}
private:
AZStd::unique_ptr<ToolsTestApplication> m_app;
};
@@ -18,6 +18,7 @@
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Viewport/ViewportId.h>
#include <AzFramework/Viewport/ClickDetector.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Viewport/ViewportTypes.h>
@@ -304,4 +305,24 @@ namespace AzToolsFramework
return entityContextId;
}
//! Maps a mouse interaction event to a ClickDetector event.
//! @note Function only cares about up or down events, all other events are mapped to Nil (ignored).
inline AzFramework::ClickDetector::ClickEvent ClickDetectorEventFromViewportInteraction(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
{
if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down)
{
return AzFramework::ClickDetector::ClickEvent::Down;
}
if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up)
{
return AzFramework::ClickDetector::ClickEvent::Up;
}
}
return AzFramework::ClickDetector::ClickEvent::Nil;
}
} // namespace AzToolsFramework
@@ -14,6 +14,7 @@
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <QApplication>
@@ -27,8 +28,11 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down)
m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction);
const auto clickOutcome = m_clickDetector.DetectClick(selectClickEvent, m_cursorState.CursorDelta());
if (clickOutcome == AzFramework::ClickDetector::ClickOutcome::Move)
{
if (m_leftMouseDown)
{
@@ -58,8 +62,7 @@ namespace AzToolsFramework
}
}
if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up)
if (clickOutcome == AzFramework::ClickDetector::ClickOutcome::Release)
{
if (m_leftMouseUp)
{
@@ -77,6 +80,8 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
m_cursorState.Update();
if (m_boxSelectRegion)
{
debugDisplay.DepthTestOff();
@@ -14,6 +14,8 @@
#include <AzCore/std/functional.h>
#include <AzCore/std/optional.h>
#include <AzFramework/Viewport/ClickDetector.h>
#include <AzFramework/Viewport/CursorState.h>
#include <AzToolsFramework/Viewport/ViewportTypes.h>
#include <QRect>
@@ -26,49 +28,49 @@ namespace AzFramework
namespace AzToolsFramework
{
/// Utility to provide box select (click and drag) support for viewport types.
/// Users can override the mouse event callbacks and display scene function to customize behavior.
//! Utility to provide box select (click and drag) support for viewport types.
//! Users can override the mouse event callbacks and display scene function to customize behavior.
class EditorBoxSelect
{
public:
EditorBoxSelect() = default;
/// Return if a box select action is currently taking place.
//! Return if a box select action is currently taking place.
bool Active() const { return m_boxSelectRegion.has_value(); }
/// Update the box select for various mouse events.
/// Call HandleMouseInteraction from type/system implementing MouseViewportRequests interface.
//! Update the box select for various mouse events.
//! Call HandleMouseInteraction from type/system implementing MouseViewportRequests interface.
void HandleMouseInteraction(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
/// Responsible for drawing the 2d box representing the selection in screen space.
//! Responsible for drawing the 2d box representing the selection in screen space.
void Display2d(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay);
/// Custom drawing behavior to happen during a box select.
//! Custom drawing behavior to happen during a box select.
void DisplayScene(
const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay);
/// Set the left mouse down callback.
//! Set the left mouse down callback.
void InstallLeftMouseDown(
const AZStd::function<void(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)>& leftMouseDown);
/// Set the mouse move callback.
//! Set the mouse move callback.
void InstallMouseMove(
const AZStd::function<void(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)>& mouseMove);
/// Set the left mouse up callback.
//! Set the left mouse up callback.
void InstallLeftMouseUp(
const AZStd::function<void()>& leftMouseUp);
/// Set the display scene callback.
//! Set the display scene callback.
void InstallDisplayScene(
const AZStd::function<void(
const AzFramework::ViewportInfo& viewportInfo,
AzFramework::DebugDisplayRequests& debugDisplay)>& displayScene);
/// Return the box select region.
/// If a box selection is being made, return the current rectangle representing the area.
/// If there is currently no active box select, then the Maybe type will be empty (there will be no region/area).
//! Return the box select region.
//! If a box selection is being made, return the current rectangle representing the area.
//! If there is currently no active box select, then the Maybe type will be empty (there will be no region/area).
const AZStd::optional<QRect>& BoxRegion() const { return m_boxSelectRegion; }
/// Return the active modifiers from the previous frame.
//! Return the active modifiers from the previous frame.
ViewportInteraction::KeyboardModifiers PreviousModifiers() const { return m_previousModifiers; }
private:
@@ -79,7 +81,9 @@ namespace AzToolsFramework
AZStd::function<void(
const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)> m_displayScene;
AZStd::optional<QRect> m_boxSelectRegion; ///< Maybe/optional value to store box select region while active.
ViewportInteraction::KeyboardModifiers m_previousModifiers; ///< Modifier keys active on the previous frame.
AZStd::optional<QRect> m_boxSelectRegion; //!< Maybe/optional value to store box select region while active.
ViewportInteraction::KeyboardModifiers m_previousModifiers; //!< Modifier keys active on the previous frame.
AzFramework::ClickDetector m_clickDetector; //!< Utility type to detect if a mouse click or move has occurred.
AzFramework::CursorState m_cursorState; //!< Utility type to track the current cursor position (and movement/delta).
};
} // namespace AzToolsFramework
@@ -1781,22 +1781,7 @@ namespace AzToolsFramework
m_cachedEntityIdUnderCursor = m_editorHelpers->HandleMouseInteraction(cameraState, mouseInteraction);
const AzFramework::ClickDetector::ClickEvent selectClickEvent = [&mouseInteraction] {
if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
{
if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down)
{
return AzFramework::ClickDetector::ClickEvent::Down;
}
if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up)
{
return AzFramework::ClickDetector::ClickEvent::Up;
}
}
return AzFramework::ClickDetector::ClickEvent::Nil;
}();
const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction);
m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
const auto clickOutcome = m_clickDetector.DetectClick(selectClickEvent, m_cursorState.CursorDelta());
@@ -0,0 +1,129 @@
/*
* 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/ToolsComponents/TransformComponent.h>
#include <Prefab/PrefabTestComponent.h>
#include <Prefab/PrefabTestDomUtils.h>
#include <Prefab/PrefabTestFixture.h>
namespace UnitTest
{
using PrefabDuplicateTest = PrefabTestFixture;
TEST_F(PrefabDuplicateTest, PrefabDuplicate_DuplicateSingleEntitySucceeds)
{
AZStd::string entityName("Same Name");
AZ::Entity* entity1 = CreateEntity(entityName.c_str());
entity1->Deactivate();
entity1->CreateComponent<PrefabTestComponent>();
entity1->Activate();
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, AzToolsFramework::EntityList{ entity1 });
AZStd::unique_ptr<Instance> newInstance = m_prefabSystemComponent->CreatePrefab(
{ entity1 },
{},
PrefabMockFilePath);
// We've created a prefab with a single Entity, so there should only be one EntityAlias in our instance
EXPECT_EQ(newInstance->GetEntityAliases().size(), 1);
// Duplicate the Entity and trigger the UpdateTemplateInstancesInQueue so the changes get propagated
m_prefabPublicInterface->DuplicateEntitiesInInstance(AzToolsFramework::EntityIdList{ entity1->GetId() });
m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
// We duplicated a single Entity, so there should now be two EntityAliases
EXPECT_EQ(newInstance->GetEntityAliases().size(), 2);
newInstance->GetConstEntities([&](const AZ::Entity& entity)
{
// Both of the entities should have the same name
EXPECT_EQ(entity.GetName(), entityName);
// Both of the entities should have the PrefabTestComponent we added
auto testComponent = entity.FindComponent<PrefabTestComponent>();
EXPECT_NE(nullptr, testComponent);
return true;
});
}
TEST_F(PrefabDuplicateTest, PrefabDuplicate_DuplicateMultipleEntitiesAndFixesReferences)
{
AZ::Entity* parentEntity = CreateEntity("Parent Entity");
AZ::Entity* childEntity = CreateEntity("Child Entity");
childEntity->Deactivate();
auto newComponent = childEntity->CreateComponent<PrefabTestComponent>();
childEntity->Activate();
// Set the EntityId reference property on our PrefabTestComponent so we can
// verify that arbitrary EntityId's are fixed up properly
newComponent->m_entityIdProperty = parentEntity->GetId();
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, AzToolsFramework::EntityList{ parentEntity, childEntity });
AZStd::unique_ptr<Instance> newInstance = m_prefabSystemComponent->CreatePrefab(
{ parentEntity, childEntity },
{},
PrefabMockFilePath);
// We've created a prefab with two entities, so there should be two EntityAliases in our instance
EXPECT_EQ(newInstance->GetEntityAliases().size(), 2);
// Duplicate the entities and trigger the UpdateTemplateInstancesInQueue so the changes get propagated
m_prefabPublicInterface->DuplicateEntitiesInInstance(AzToolsFramework::EntityIdList{ parentEntity->GetId(), childEntity->GetId() });
m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
// We duplicated two entities, so there should now be four EntityAliases
EXPECT_EQ(newInstance->GetEntityAliases().size(), 4);
AzToolsFramework::EntityIdList parentEntityIds;
newInstance->GetConstEntities([&](const AZ::Entity& entity)
{
// Gather the parent EntityIds by tracking which entities don't have a PrefabTestComponent
auto testComponent = entity.FindComponent<PrefabTestComponent>();
if (!testComponent)
{
parentEntityIds.push_back(entity.GetId());
}
return true;
});
// There should only be two parents
EXPECT_EQ(parentEntityIds.size(), 2);
// Verify that the EntityId reference on the PrefabTestComponent on the children correspond
// to unique entities, which will verify that the EntityIds are fixed up on duplicate
newInstance->GetConstEntities([&](const AZ::Entity& entity)
{
// Only the child entities have a PrefabTestComponent
auto testComponent = entity.FindComponent<PrefabTestComponent>();
if (testComponent)
{
auto it = AZStd::find(parentEntityIds.begin(), parentEntityIds.end(), testComponent->m_entityIdProperty);
EXPECT_NE(it, parentEntityIds.end());
// Erase when we find it so that the matches will be unique
parentEntityIds.erase(it);
}
return true;
});
// Verify we matched each of the parent EntityIds
EXPECT_EQ(parentEntityIds.size(), 0);
}
}
@@ -20,6 +20,17 @@
namespace UnitTest
{
PrefabTestToolsApplication::PrefabTestToolsApplication(AZStd::string appName)
: ToolsTestApplication(AZStd::move(appName))
{
}
bool PrefabTestToolsApplication::IsPrefabSystemEnabled() const
{
// Make sure our prefab tests always run with prefabs enabled
return true;
}
void PrefabTestFixture::SetUpEditorFixtureImpl()
{
// Acquire the system entity
@@ -32,6 +43,9 @@ namespace UnitTest
m_prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
EXPECT_TRUE(m_prefabLoaderInterface);
m_prefabPublicInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabPublicInterface>::Get();
EXPECT_TRUE(m_prefabPublicInterface);
m_instanceUpdateExecutorInterface = AZ::Interface<AzToolsFramework::Prefab::InstanceUpdateExecutorInterface>::Get();
EXPECT_TRUE(m_instanceUpdateExecutorInterface);
@@ -41,6 +55,11 @@ namespace UnitTest
GetApplication()->RegisterComponentDescriptor(PrefabTestComponent::CreateDescriptor());
}
AZStd::unique_ptr<ToolsTestApplication> PrefabTestFixture::CreateTestApplication()
{
return AZStd::make_unique<PrefabTestToolsApplication>("PrefabTestApplication");
}
AZ::Entity* PrefabTestFixture::CreateEntity(const char* entityName, const bool shouldActivate)
{
// Circumvent the EntityContext system and generate a new entity with a transformcomponent
@@ -31,6 +31,16 @@ namespace UnitTest
using namespace AzToolsFramework::Prefab;
using namespace PrefabTestUtils;
class PrefabTestToolsApplication
: public ToolsTestApplication
{
public:
PrefabTestToolsApplication(AZStd::string appName);
// Make sure our prefab tests always run with prefabs enabled
bool IsPrefabSystemEnabled() const override;
};
class PrefabTestFixture
: public ToolsApplicationFixture,
public UnitTest::TraceBusRedirector
@@ -45,6 +55,8 @@ namespace UnitTest
void SetUpEditorFixtureImpl() override;
AZStd::unique_ptr<ToolsTestApplication> CreateTestApplication() override;
AZ::Entity* CreateEntity(const char* entityName, const bool shouldActivate = true);
void CompareInstances(const Instance& instanceA, const Instance& instanceB, bool shouldCompareLinkIds = true,
@@ -57,6 +69,7 @@ namespace UnitTest
PrefabSystemComponent* m_prefabSystemComponent = nullptr;
PrefabLoaderInterface* m_prefabLoaderInterface = nullptr;
PrefabPublicInterface* m_prefabPublicInterface = nullptr;
InstanceUpdateExecutorInterface* m_instanceUpdateExecutorInterface = nullptr;
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
};
@@ -54,6 +54,7 @@ set(FILES
Prefab/Spawnable/SpawnableMetaDataTests.cpp
Prefab/MockPrefabFileIOActionValidator.cpp
Prefab/MockPrefabFileIOActionValidator.h
Prefab/PrefabDuplicateTests.cpp
Prefab/PrefabEntityAliasTests.cpp
Prefab/PrefabInstanceToTemplatePropagatorTests.cpp
Prefab/PrefabInstantiateTests.cpp
+87
View File
@@ -0,0 +1,87 @@
/*
* 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/UnitTest/TestTypes.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Viewport/CameraInput.h>
namespace UnitTest
{
class CameraInputFixture : public AllocatorsTestFixture
{
public:
AzFramework::Camera m_camera;
AzFramework::Camera m_targetCamera;
AZStd::shared_ptr<AzFramework::CameraSystem> m_cameraSystem;
bool HandleEventAndUpdate(const AzFramework::InputEvent& event)
{
constexpr float deltaTime = 0.01666f; // 60fps
const bool consumed = m_cameraSystem->HandleEvents(event);
m_camera = m_cameraSystem->StepCamera(m_targetCamera, deltaTime);
return consumed;
}
void SetUp() override
{
AllocatorsTestFixture::SetUp();
AzFramework::ReloadCameraKeyBindings();
m_cameraSystem = AZStd::make_shared<AzFramework::CameraSystem>();
auto firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Right);
auto firstPersonTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::LookTranslation);
auto orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>();
auto orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Left);
auto orbitTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::OrbitTranslation);
orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera);
orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera);
m_cameraSystem->m_cameras.AddCamera(firstPersonRotateCamera);
m_cameraSystem->m_cameras.AddCamera(firstPersonTranslateCamera);
m_cameraSystem->m_cameras.AddCamera(orbitCamera);
}
void TearDown() override
{
m_cameraSystem->m_cameras.Clear();
m_cameraSystem.reset();
AllocatorsTestFixture::TearDown();
}
};
TEST_F(CameraInputFixture, BeginEndOrbitCameraConsumesCorrectEvents)
{
// begin orbit camera
const bool consumed1 = HandleEventAndUpdate(
AzFramework::DiscreteInputEvent{AzFramework::InputDeviceKeyboard::Key::ModifierAltL, AzFramework::InputChannel::State::Began});
// begin listening for orbit rotate (click detector) - event is not consumed
const bool consumed2 = HandleEventAndUpdate(
AzFramework::DiscreteInputEvent{AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began});
// begin orbit rotate (mouse has moved sufficient distance to initiate)
const bool consumed3 = HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{5});
// end orbit (mouse up) - event is not consumed
const bool consumed4 = HandleEventAndUpdate(
AzFramework::DiscreteInputEvent{AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended});
const auto allConsumed = AZStd::vector<bool>{consumed1, consumed2, consumed3, consumed4};
using ::testing::ElementsAre;
EXPECT_THAT(allConsumed, ElementsAre(true, false, true, false));
}
} // namespace UnitTest
@@ -139,4 +139,21 @@ namespace UnitTest
EXPECT_THAT(secondaryDownOutcome, Eq(ClickDetector::ClickOutcome::Nil)); // ignored double click
EXPECT_THAT(secondaryUpOutcome, Eq(ClickDetector::ClickOutcome::Nil)); // click not registered
}
// if the click detector registers a mouse down event, but then all intermediate calls are ignored
// (another system may start intercepting events and swallowing them) then when we do receive a mouse
// up event we should ensure we take into account the current delta - if the delta is large, then the
// outcome will be release
TEST_F(ClickDetectorFixture, ClickIsNotRegisteredAfterIgnoringMouseMovesBeforeMouseUpWithLargeDelta)
{
using ::testing::Eq;
const ClickDetector::ClickOutcome downOutcome =
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Down, ScreenVector(0, 0));
const ClickDetector::ClickOutcome upOutcome =
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Up, ScreenVector(50, 50));
EXPECT_THAT(downOutcome, Eq(ClickDetector::ClickOutcome::Nil));
EXPECT_THAT(upOutcome, Eq(ClickDetector::ClickOutcome::Release));
}
} // namespace UnitTest
@@ -17,6 +17,7 @@ set(FILES
BinToTextEncode.cpp
ComponentAddRemove.cpp
ComponentAdapterTests.cpp
CameraInputTests.cpp
ClickDetectorTests.cpp
CursorStateTests.cpp
EntityContext.cpp