Merge branch 'development' into LYN-5265_state_tracker_impl
This commit is contained in:
@@ -173,6 +173,7 @@ namespace AZ
|
||||
|
||||
if (assetTracker)
|
||||
{
|
||||
assetTracker->FixUpAsset(*instance);
|
||||
assetTracker->AddAsset(*instance);
|
||||
}
|
||||
|
||||
@@ -185,7 +186,20 @@ namespace AZ
|
||||
return context.Report(result, message);
|
||||
}
|
||||
|
||||
void SerializedAssetTracker::AddAsset(Asset<AssetData>& asset)
|
||||
void SerializedAssetTracker::SetAssetFixUp(AssetFixUp assetFixUpCallback)
|
||||
{
|
||||
m_assetFixUpCallback = AZStd::move(assetFixUpCallback);
|
||||
}
|
||||
|
||||
void SerializedAssetTracker::FixUpAsset(Asset<AssetData>& asset)
|
||||
{
|
||||
if (m_assetFixUpCallback)
|
||||
{
|
||||
m_assetFixUpCallback(asset);
|
||||
}
|
||||
}
|
||||
|
||||
void SerializedAssetTracker::AddAsset(Asset<AssetData> asset)
|
||||
{
|
||||
m_serializedAssets.emplace_back(asset);
|
||||
}
|
||||
@@ -199,5 +213,6 @@ namespace AZ
|
||||
{
|
||||
return m_serializedAssets;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
} // namespace AZ
|
||||
|
||||
@@ -39,13 +39,18 @@ namespace AZ
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(SerializedAssetTracker, "{1E067091-8C0A-44B1-A455-6E97663F6963}");
|
||||
using AssetFixUp = AZStd::function<void(Asset<AssetData>& asset)>;
|
||||
|
||||
void AddAsset(Asset<AssetData>& asset);
|
||||
void SetAssetFixUp(AssetFixUp assetFixUpCallback);
|
||||
void FixUpAsset(Asset<AssetData>& asset);
|
||||
|
||||
void AddAsset(Asset<AssetData> asset);
|
||||
AZStd::vector<Asset<AssetData>>& GetTrackedAssets();
|
||||
const AZStd::vector<Asset<AssetData>>& GetTrackedAssets() const;
|
||||
|
||||
private:
|
||||
AZStd::vector<Asset<AssetData>> m_serializedAssets;
|
||||
AssetFixUp m_assetFixUpCallback;
|
||||
};
|
||||
} // namespace Data
|
||||
} // namespace AZ
|
||||
|
||||
@@ -476,15 +476,16 @@ namespace AZ
|
||||
|
||||
// Responsible for using the Json Serialization Issue Callback system
|
||||
// to determine when a JSON Patch or JSON Merge Patch modifies a value
|
||||
// at a path underneath the IConsole::ConsoleRootCommandKey JSON pointer
|
||||
// at a path underneath the IConsole::ConsoleRuntimeCommandKey JSON pointer
|
||||
JsonSerializationResult::ResultCode operator()(AZStd::string_view message,
|
||||
JsonSerializationResult::ResultCode result, AZStd::string_view path)
|
||||
{
|
||||
AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator };
|
||||
constexpr AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRuntimeCommandKey, AZ::IO::PosixPathSeparator };
|
||||
constexpr AZ::IO::PathView consoleAutoexecCommandKey{ IConsole::ConsoleAutoexecCommandKey, AZ::IO::PosixPathSeparator };
|
||||
AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator };
|
||||
if (result.GetTask() == JsonSerializationResult::Tasks::Merge
|
||||
&& result.GetProcessing() == JsonSerializationResult::Processing::Completed
|
||||
&& inputKey.IsRelativeTo(consoleRootCommandKey))
|
||||
&& (inputKey.IsRelativeTo(consoleRootCommandKey) || inputKey.IsRelativeTo(consoleAutoexecCommandKey)))
|
||||
{
|
||||
if (auto type = m_settingsRegistry.GetType(path); type != SettingsRegistryInterface::Type::NoType)
|
||||
{
|
||||
@@ -510,12 +511,24 @@ namespace AZ
|
||||
{
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
|
||||
AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator };
|
||||
constexpr AZ::IO::PathView consoleRuntimeCommandKey{ IConsole::ConsoleRuntimeCommandKey, AZ::IO::PosixPathSeparator };
|
||||
constexpr AZ::IO::PathView consoleAutoexecCommandKey{ IConsole::ConsoleAutoexecCommandKey, AZ::IO::PosixPathSeparator };
|
||||
AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator };
|
||||
// The ConsoleRootComamndKey is not a command itself so strictly children keys are being examined
|
||||
if (inputKey.IsRelativeTo(consoleRootCommandKey) && inputKey != consoleRootCommandKey)
|
||||
|
||||
// Abuses the IsRelativeToFuncton function of the path class to extract the console
|
||||
// command from the settings registry objects
|
||||
FixedValueString command;
|
||||
if (inputKey != consoleRuntimeCommandKey && inputKey.IsRelativeTo(consoleRuntimeCommandKey))
|
||||
{
|
||||
command = inputKey.LexicallyRelative(consoleRuntimeCommandKey).Native();
|
||||
}
|
||||
else if (inputKey != consoleAutoexecCommandKey && inputKey.IsRelativeTo(consoleAutoexecCommandKey))
|
||||
{
|
||||
command = inputKey.LexicallyRelative(consoleAutoexecCommandKey).Native();
|
||||
}
|
||||
|
||||
if (!command.empty())
|
||||
{
|
||||
FixedValueString command = inputKey.LexicallyRelative(consoleRootCommandKey).Native();
|
||||
ConsoleCommandContainer commandArgs;
|
||||
// Argument string which stores the value from the Settings Registry long enough
|
||||
// to pass into the PerformCommand. The ConsoleCommandContainer stores string_views
|
||||
@@ -603,9 +616,10 @@ namespace AZ
|
||||
|
||||
void Console::RegisterCommandInvokerWithSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry)
|
||||
{
|
||||
// Make sure the there is a JSON object at the path of AZ::IConsole::ConsoleRootCommandKey
|
||||
// Make sure the there is a JSON object at the ConsoleRuntimeCommandKey or ConsoleAutoexecKey
|
||||
// So that JSON Patch is able to add values underneath that object (JSON Patch doesn't create intermediate objects)
|
||||
settingsRegistry.MergeSettings(R"({ "Amazon": { "AzCore": { "Runtime": { "ConsoleCommands": {} } }}})",
|
||||
settingsRegistry.MergeSettings(R"({ "Amazon": { "AzCore": { "Runtime": { "ConsoleCommands": {} } } })"
|
||||
R"(,"O3DE": { "Autoexec": { "ConsoleCommands": {} } } })",
|
||||
SettingsRegistryInterface::Format::JsonMergePatch);
|
||||
m_consoleCommandKeyHandler = settingsRegistry.RegisterNotifier(ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this });
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ namespace AZ
|
||||
|
||||
using FunctorVisitor = AZStd::function<void(ConsoleFunctorBase*)>;
|
||||
|
||||
inline static constexpr AZStd::string_view ConsoleRootCommandKey = "/Amazon/AzCore/Runtime/ConsoleCommands";
|
||||
inline static constexpr AZStd::string_view ConsoleRuntimeCommandKey = "/Amazon/AzCore/Runtime/ConsoleCommands";
|
||||
inline static constexpr AZStd::string_view ConsoleAutoexecCommandKey = "/O3DE/Autoexec/ConsoleCommands";
|
||||
|
||||
IConsole() = default;
|
||||
virtual ~IConsole() = default;
|
||||
|
||||
@@ -224,6 +224,8 @@ namespace AZ
|
||||
|
||||
void Debug::Trace::Terminate(int exitCode)
|
||||
{
|
||||
AZ_TracePrintf("Exit", "Called Terminate() with exit code: 0x%x", exitCode);
|
||||
AZ::Debug::Trace::PrintCallstack("Exit");
|
||||
Platform::Terminate(exitCode);
|
||||
}
|
||||
|
||||
|
||||
@@ -160,8 +160,8 @@ namespace AZ
|
||||
/**
|
||||
* Locking primitive that is used when executing events in the event queue.
|
||||
*/
|
||||
using EventQueueMutexType = typename AZStd::Utils::if_c<AZStd::is_same<typename Traits::EventQueueMutexType, NullMutex>::value, // if EventQueueMutexType==NullMutex use MutexType otherwise EventQueueMutexType
|
||||
MutexType, typename Traits::EventQueueMutexType>::type;
|
||||
using EventQueueMutexType = AZStd::conditional_t<AZStd::is_same<typename Traits::EventQueueMutexType, NullMutex>::value, // if EventQueueMutexType==NullMutex use MutexType otherwise EventQueueMutexType
|
||||
MutexType, typename Traits::EventQueueMutexType>;
|
||||
|
||||
/**
|
||||
* Pointer to an address on the bus.
|
||||
@@ -180,14 +180,22 @@ namespace AZ
|
||||
* `<BusName>::ExecuteQueuedEvents()`.
|
||||
* By default, the event queue is disabled.
|
||||
*/
|
||||
static const bool EnableEventQueue = Traits::EnableEventQueue;
|
||||
static const bool EventQueueingActiveByDefault = Traits::EventQueueingActiveByDefault;
|
||||
static const bool EnableQueuedReferences = Traits::EnableQueuedReferences;
|
||||
static constexpr bool EnableEventQueue = Traits::EnableEventQueue;
|
||||
static constexpr bool EventQueueingActiveByDefault = Traits::EventQueueingActiveByDefault;
|
||||
static constexpr bool EnableQueuedReferences = Traits::EnableQueuedReferences;
|
||||
|
||||
/**
|
||||
* True if the EBus supports more than one address. Otherwise, false.
|
||||
*/
|
||||
static const bool HasId = Traits::AddressPolicy != EBusAddressPolicy::Single;
|
||||
static constexpr bool HasId = Traits::AddressPolicy != EBusAddressPolicy::Single;
|
||||
|
||||
/**
|
||||
* Template Lock Guard class that wraps around the Mutex
|
||||
* The EBus uses for Dispatching Events.
|
||||
* This is not the EBus Context Mutex if LocklessDispatch is true
|
||||
*/
|
||||
template <typename DispatchMutex>
|
||||
using DispatchLockGuard = typename Traits::template DispatchLockGuard<DispatchMutex, Traits::LocklessDispatch>;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -460,7 +468,7 @@ namespace AZ
|
||||
using BusPtr = typename Traits::BusPtr;
|
||||
|
||||
/**
|
||||
* Helper to queue an event by BusIdType only when function queueing is enabled
|
||||
* Helper to queue an event by BusIdType only when function queueing is enabled
|
||||
* @param id Address ID. Handlers that are connected to this ID will receive the event.
|
||||
* @param func Function pointer of the event to dispatch.
|
||||
* @param args Function arguments that are passed to each handler.
|
||||
@@ -581,7 +589,7 @@ namespace AZ
|
||||
, public EBusBroadcaster<Bus, Traits>
|
||||
, public EBusEventer<Bus, Traits>
|
||||
, public EBusEventEnumerator<Bus, Traits>
|
||||
, public AZStd::Utils::if_c<Traits::EnableEventQueue, EBusEventQueue<Bus, Traits>, EBusNullQueue>::type
|
||||
, public AZStd::conditional_t<Traits::EnableEventQueue, EBusEventQueue<Bus, Traits>, EBusNullQueue>
|
||||
{
|
||||
};
|
||||
|
||||
@@ -599,7 +607,7 @@ namespace AZ
|
||||
: public EventDispatcher<Bus, Traits>
|
||||
, public EBusBroadcaster<Bus, Traits>
|
||||
, public EBusBroadcastEnumerator<Bus, Traits>
|
||||
, public AZStd::Utils::if_c<Traits::EnableEventQueue, EBusBroadcastQueue<Bus, Traits>, EBusNullQueue>::type
|
||||
, public AZStd::conditional_t<Traits::EnableEventQueue, EBusBroadcastQueue<Bus, Traits>, EBusNullQueue>
|
||||
{
|
||||
};
|
||||
|
||||
|
||||
@@ -236,6 +236,17 @@ namespace AZ
|
||||
* code before or after an event.
|
||||
*/
|
||||
using EventProcessingPolicy = EBusEventProcessingPolicy;
|
||||
|
||||
/**
|
||||
* Template Lock Guard class that wraps around the Mutex
|
||||
* The EBus Context uses the LockGuard when dispatching
|
||||
* (either AZStd::scoped_lock<MutexType> or NullLockGuard<MutexType>)
|
||||
* The IsLocklessDispatch bool is there to defer evaluation of the LocklessDispatch constant
|
||||
* Otherwise the value above in EBusTraits.h is always used and not the value
|
||||
* that the derived trait class sets.
|
||||
*/
|
||||
template <typename DispatchMutex, bool IsLocklessDispatch>
|
||||
using DispatchLockGuard = AZStd::conditional_t<IsLocklessDispatch, AZ::Internal::NullLockGuard<DispatchMutex>, AZStd::scoped_lock<DispatchMutex>>;
|
||||
};
|
||||
|
||||
namespace Internal
|
||||
@@ -496,6 +507,14 @@ namespace AZ
|
||||
*/
|
||||
static const bool HasId = Traits::AddressPolicy != EBusAddressPolicy::Single;
|
||||
|
||||
/**
|
||||
* Template Lock Guard class that wraps around the Mutex
|
||||
* The EBus uses for Dispatching Events.
|
||||
* This is not EBus Context Mutex when LocklessDispatch is set
|
||||
*/
|
||||
template <typename DispatchMutex>
|
||||
using DispatchLockGuard = typename ImplTraits::template DispatchLockGuard<DispatchMutex>;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Check to help identify common mistakes
|
||||
/// @cond EXCLUDE_DOCS
|
||||
@@ -620,11 +639,11 @@ namespace AZ
|
||||
using ContextMutexType = AZStd::conditional_t<BusTraits::LocklessDispatch && AZStd::is_same_v<MutexType, AZ::NullMutex>, AZStd::shared_mutex, MutexType>;
|
||||
|
||||
/**
|
||||
* The scoped lock guard to use (either AZStd::scoped_lock<MutexType> or NullLockGuard<MutexType>
|
||||
* The scoped lock guard to use
|
||||
* during broadcast/event dispatch.
|
||||
* @see EBusTraits::LocklessDispatch
|
||||
*/
|
||||
using DispatchLockGuard = AZStd::conditional_t<BusTraits::LocklessDispatch, AZ::Internal::NullLockGuard<ContextMutexType>, AZStd::scoped_lock<ContextMutexType>>;
|
||||
using DispatchLockGuard = DispatchLockGuard<ContextMutexType>;
|
||||
|
||||
/**
|
||||
* The scoped lock guard to use during connection. Some specialized policies execute handler methods which
|
||||
@@ -704,6 +723,11 @@ namespace AZ
|
||||
static Context& GetOrCreateContext(bool trackCallstack=true);
|
||||
|
||||
static bool IsInDispatch(Context* context = GetContext(false));
|
||||
|
||||
/**
|
||||
* Returns whether the EBus context is in the middle of a dispatch on the current thread
|
||||
*/
|
||||
static bool IsInDispatchThisThread(Context* context = GetContext(false));
|
||||
/// @cond EXCLUDE_DOCS
|
||||
struct RouterCallstackEntry
|
||||
: public CallstackEntry
|
||||
@@ -1208,6 +1232,13 @@ AZ_POP_DISABLE_WARNING
|
||||
return context != nullptr && context->m_dispatches > 0;
|
||||
}
|
||||
|
||||
template<class Interface, class Traits>
|
||||
bool EBus<Interface, Traits>::IsInDispatchThisThread(Context* context)
|
||||
{
|
||||
return context != nullptr && context->s_callstack != nullptr
|
||||
&& context->s_callstack->m_prev != nullptr;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
template<class Interface, class Traits>
|
||||
EBus<Interface, Traits>::RouterCallstackEntry::RouterCallstackEntry(Iterator it, const BusIdType* busId, bool isQueued, bool isReverse)
|
||||
|
||||
@@ -18,6 +18,14 @@
|
||||
#include <AzCore/std/parallel/thread.h>
|
||||
#include <AzCore/Math/MathUtils.h>
|
||||
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
|
||||
#include <AzCore/Threading/ThreadUtils.h>
|
||||
|
||||
AZ_CVAR(float, cl_jobThreadsConcurrencyRatio, 0.6f, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system multiplier on the number of hw threads the machine creates at initialization");
|
||||
AZ_CVAR(uint32_t, cl_jobThreadsNumReserved, 2, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system number of hardware threads that are reserved for O3DE system threads");
|
||||
AZ_CVAR(uint32_t, cl_jobThreadsMinNumber, 2, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system minimum number of worker threads to create after scaling the number of hw threads");
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
//=========================================================================
|
||||
@@ -46,9 +54,10 @@ namespace AZ
|
||||
JobManagerThreadDesc threadDesc;
|
||||
|
||||
int numberOfWorkerThreads = m_numberOfWorkerThreads;
|
||||
if (numberOfWorkerThreads <= 0)
|
||||
if (numberOfWorkerThreads <= 0) // spawn default number of threads
|
||||
{
|
||||
numberOfWorkerThreads = AZ::GetMin(static_cast<unsigned int>(desc.m_workerThreads.capacity()), AZStd::thread::hardware_concurrency());
|
||||
uint32_t scaledHardwareThreads = Threading::CalcNumWorkerThreads(cl_jobThreadsConcurrencyRatio, cl_jobThreadsMinNumber, cl_jobThreadsNumReserved);
|
||||
numberOfWorkerThreads = AZ::GetMin(static_cast<unsigned int>(desc.m_workerThreads.capacity()), scaledHardwareThreads);
|
||||
#if (AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS)
|
||||
numberOfWorkerThreads = AZ::GetMin(numberOfWorkerThreads, AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS);
|
||||
#endif // (AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS)
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace AZ
|
||||
*/
|
||||
int m_stackSize;
|
||||
|
||||
JobManagerThreadDesc(int cpuId = -1, int priority = -100000, int stackSize = -1)
|
||||
JobManagerThreadDesc(int cpuId = -1, int priority = 0, int stackSize = -1)
|
||||
: m_cpuId(cpuId)
|
||||
, m_priority(priority)
|
||||
, m_stackSize(stackSize)
|
||||
|
||||
@@ -135,18 +135,12 @@ namespace AZ
|
||||
{
|
||||
stringLength = strlen(uuidString);
|
||||
}
|
||||
if (stringLength > MaxPermissiveStringSize)
|
||||
{
|
||||
if (!skipWarnings)
|
||||
{
|
||||
AZ_Warning("Math", false, "Can't create UUID from string length %zu over maximum %zu", stringLength, MaxPermissiveStringSize);
|
||||
}
|
||||
return Uuid::CreateNull();
|
||||
}
|
||||
|
||||
size_t newLength{ 0 };
|
||||
char createString[MaxPermissiveStringSize];
|
||||
|
||||
for (size_t curPos = 0; curPos < stringLength; ++curPos)
|
||||
// Loop until we get to the end of the string OR stop once we've accumulated a full UUID string worth of data
|
||||
for (size_t curPos = 0; curPos < stringLength && newLength < ValidUuidStringLength; ++curPos)
|
||||
{
|
||||
char curChar = uuidString[curPos];
|
||||
switch (curChar)
|
||||
|
||||
@@ -42,8 +42,9 @@ namespace AZ
|
||||
//VER_AZ_RANDOM_CRC32 = 6, // 0 1 1 0
|
||||
};
|
||||
|
||||
static constexpr int ValidUuidStringLength = 32; /// Number of characters (data only, no extra formatting) in a valid UUID string
|
||||
static const size_t MaxStringBuffer = 39; /// 32 Uuid + 4 dashes + 2 brackets + 1 terminate
|
||||
|
||||
|
||||
Uuid() {}
|
||||
Uuid(const char* string, size_t stringLength = 0) { *this = CreateString(string, stringLength); }
|
||||
|
||||
|
||||
@@ -204,14 +204,14 @@ namespace AZ
|
||||
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(const PreMergeEventCallback& callback) = 0;
|
||||
//! Register a function that will be called before a file is merged.
|
||||
//! @callback The function to call before a file is merged.
|
||||
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent (PreMergeEventCallback&& callback) = 0;
|
||||
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback&& callback) = 0;
|
||||
|
||||
//! Register a function that will be called after a file is merged.
|
||||
//! @callback The function to call after a file is merged.
|
||||
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(const PostMergeEventCallback& callback) = 0;
|
||||
//! Register a function that will be called after a file is merged.
|
||||
//! @callback The function to call after a file is merged.
|
||||
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent (PostMergeEventCallback&& callback) = 0;
|
||||
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback&& callback) = 0;
|
||||
|
||||
//! Gets the boolean value at the provided path.
|
||||
//! @param result The target to write the result to.
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Settings/SettingsRegistryVisitorUtils.h>
|
||||
|
||||
|
||||
namespace AZ::SettingsRegistryVisitorUtils
|
||||
{
|
||||
// Field Visitor implementation
|
||||
FieldVisitor::FieldVisitor() = default;
|
||||
FieldVisitor::FieldVisitor(VisitFieldType visitFieldType)
|
||||
: m_visitFieldType{ visitFieldType }
|
||||
{
|
||||
}
|
||||
|
||||
auto FieldVisitor::Traverse(AZStd::string_view path, AZStd::string_view valueName,
|
||||
VisitAction action, Type type) -> VisitResponse
|
||||
{
|
||||
// A default response skip prevents visiting grand children(depth 2 or lower)
|
||||
VisitResponse visitResponse = VisitResponse::Skip;
|
||||
if (action == VisitAction::Begin)
|
||||
{
|
||||
// Invoke FieldVisitor override if the root path has been set
|
||||
if (m_rootPath.has_value())
|
||||
{
|
||||
Visit(path, valueName, type);
|
||||
}
|
||||
// To make sure only the direct children are visited(depth 1)
|
||||
// set the root path once and set the VisitReponsoe
|
||||
// to Continue to recurse into is fields
|
||||
if (!m_rootPath.has_value())
|
||||
{
|
||||
bool visitableFieldType{};
|
||||
switch (m_visitFieldType)
|
||||
{
|
||||
case VisitFieldType::Array:
|
||||
visitableFieldType = type == Type::Array;
|
||||
break;
|
||||
case VisitFieldType::Object:
|
||||
visitableFieldType = type == Type::Object;
|
||||
break;
|
||||
case VisitFieldType::ArrayOrObject:
|
||||
visitableFieldType = type == Type::Array || type ==Type::Object;
|
||||
break;
|
||||
default:
|
||||
AZ_Error("FieldVisitor", false, "The field visitation type value is invalid");
|
||||
break;
|
||||
}
|
||||
|
||||
if (visitableFieldType)
|
||||
{
|
||||
m_rootPath = path;
|
||||
visitResponse = VisitResponse::Continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (action == VisitAction::Value)
|
||||
{
|
||||
// Invoke FieldVisitor override if the root path has been set
|
||||
if (m_rootPath.has_value())
|
||||
{
|
||||
Visit(path, valueName, type);
|
||||
}
|
||||
}
|
||||
else if (action == VisitAction::End)
|
||||
{
|
||||
// Reset m_rootPath back to null when the root path has finished being visited
|
||||
if (m_rootPath.has_value() && *m_rootPath == path)
|
||||
{
|
||||
m_rootPath = AZStd::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return visitResponse;
|
||||
}
|
||||
|
||||
// Array Visitor implementation
|
||||
ArrayVisitor::ArrayVisitor()
|
||||
: FieldVisitor(VisitFieldType::Array)
|
||||
{
|
||||
}
|
||||
|
||||
// Object Visitor implementation
|
||||
ObjectVisitor::ObjectVisitor()
|
||||
: FieldVisitor(VisitFieldType::Object)
|
||||
{
|
||||
}
|
||||
|
||||
// Generic VisitField Callback implemention
|
||||
template <typename BaseVisitor>
|
||||
bool VisitFieldCallback(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path)
|
||||
{
|
||||
struct VisitFieldVisitor
|
||||
: BaseVisitor
|
||||
{
|
||||
using BaseVisitor::Visit;
|
||||
VisitFieldVisitor(const VisitorCallback& visitCallback)
|
||||
: m_visitCallback{ visitCallback }
|
||||
{}
|
||||
|
||||
void Visit(AZStd::string_view path, AZStd::string_view fieldIndex, typename BaseVisitor::Type type) override
|
||||
{
|
||||
m_visitCallback(path, fieldIndex, type);
|
||||
}
|
||||
|
||||
const VisitorCallback& m_visitCallback;
|
||||
};
|
||||
|
||||
VisitFieldVisitor visitor{ visitCallback };
|
||||
return settingsRegistry.Visit(visitor, path);
|
||||
}
|
||||
|
||||
// VisitField implementation
|
||||
bool VisitField(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path)
|
||||
{
|
||||
return VisitFieldCallback<FieldVisitor>(settingsRegistry, visitCallback, path);
|
||||
}
|
||||
|
||||
// VisitArray implementation
|
||||
bool VisitArray(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path)
|
||||
{
|
||||
return VisitFieldCallback<ArrayVisitor>(settingsRegistry, visitCallback, path);
|
||||
}
|
||||
|
||||
// VisitObject implementation
|
||||
bool VisitObject(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path)
|
||||
{
|
||||
return VisitFieldCallback<ObjectVisitor>(settingsRegistry, visitCallback, path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
|
||||
|
||||
namespace AZ::SettingsRegistryVisitorUtils
|
||||
{
|
||||
//! Interface for visiting the fields of an array or object
|
||||
//! To access the values, use the SettingsRegistryInterface Get/GetObject methods
|
||||
struct FieldVisitor
|
||||
: public AZ::SettingsRegistryInterface::Visitor
|
||||
{
|
||||
using VisitResponse = AZ::SettingsRegistryInterface::VisitResponse;
|
||||
using VisitAction = AZ::SettingsRegistryInterface::VisitAction;
|
||||
using Type = AZ::SettingsRegistryInterface::Type;
|
||||
|
||||
FieldVisitor();
|
||||
|
||||
// Bring the base class visitor functions into scope
|
||||
using AZ::SettingsRegistryInterface::Visitor::Visit;
|
||||
virtual void Visit(AZStd::string_view path, AZStd::string_view arrayIndex, Type type) = 0;
|
||||
|
||||
protected:
|
||||
// VisitFieldType is used for filtering the type of referenced by the root path
|
||||
enum class VisitFieldType
|
||||
{
|
||||
Array,
|
||||
Object,
|
||||
ArrayOrObject
|
||||
};
|
||||
FieldVisitor(const VisitFieldType visitFieldType);
|
||||
private:
|
||||
VisitResponse Traverse(AZStd::string_view path, AZStd::string_view valueName,
|
||||
VisitAction action, Type type) override;
|
||||
|
||||
VisitFieldType m_visitFieldType{ VisitFieldType::ArrayOrObject };
|
||||
AZStd::optional<AZ::SettingsRegistryInterface::FixedValueString> m_rootPath;
|
||||
};
|
||||
|
||||
//! Interface for visiting the fields of an array
|
||||
//! To access the values, use the SettingsRegistryInterface Get/GetObject methods
|
||||
struct ArrayVisitor
|
||||
: public FieldVisitor
|
||||
{
|
||||
ArrayVisitor();
|
||||
};
|
||||
|
||||
//! Interface for visiting the fields of an object
|
||||
//! To access the values, use the SettingsRegistryInterface Get/GetObject methods
|
||||
struct ObjectVisitor
|
||||
: public FieldVisitor
|
||||
{
|
||||
ObjectVisitor();
|
||||
};
|
||||
|
||||
//! Signature of callback funcition invoked when visiting an element of an array or object
|
||||
using VisitorCallback = AZStd::function<void(AZStd::string_view path, AZStd::string_view fieldName,
|
||||
AZ::SettingsRegistryInterface::Type)>;
|
||||
|
||||
//! Invokes the visitor callback for each element of either the array or object at @path
|
||||
//! If @path is not an array or object, then no elements are visited
|
||||
//! This function will not recurse into children of elements
|
||||
//! @visitCallback functor that is invoked for each array or object element found
|
||||
bool VisitField(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path);
|
||||
//! Invokes the visitor callback for each element of the array at @path
|
||||
//! If @path is not an array, then no elements are visited
|
||||
//! This function will not recurse into children of elements
|
||||
//! @visitCallback functor that is invoked for each array element found
|
||||
bool VisitArray(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path);
|
||||
//! Invokes the visitor callback for each element of the object at @path
|
||||
//! If @path is not an object, then no elements are visited
|
||||
//! This function will not recurse into children of elements
|
||||
//! @visitCallback functor that is invoked for each object element found
|
||||
bool VisitObject(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path);
|
||||
}
|
||||
@@ -308,11 +308,11 @@ namespace AZ
|
||||
|
||||
void TaskExecutor::SetInstance(TaskExecutor* executor)
|
||||
{
|
||||
if (!executor)
|
||||
if (!executor) // allow unsetting the executor
|
||||
{
|
||||
s_executor.Reset();
|
||||
}
|
||||
else if (!s_executor) // ignore any calls to set after the first (this happens in unit tests that create new system entities)
|
||||
else if (!s_executor) // ignore any extra executors after the first (this happens during unit tests)
|
||||
{
|
||||
s_executor = AZ::Environment::CreateVariable<TaskExecutor*>(s_executorName, executor);
|
||||
}
|
||||
|
||||
@@ -11,9 +11,15 @@
|
||||
#include <AzCore/Task/TaskGraphSystemComponent.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Math/MathUtils.h>
|
||||
#include <AzCore/Threading/ThreadUtils.h>
|
||||
|
||||
// Create a cvar as a central location for experimentation with switching from the Job system to TaskGraph system.
|
||||
AZ_CVAR(bool, cl_activateTaskGraph, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Flag clients of TaskGraph to switch between jobs/taskgraph (Note does not disable task graph system)");
|
||||
AZ_CVAR(float, cl_taskGraphThreadsConcurrencyRatio, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "TaskGraph calculate the number of worker threads to spawn by scaling the number of hw threads, value is clamped between 0.0f and 1.0f");
|
||||
AZ_CVAR(uint32_t, cl_taskGraphThreadsNumReserved, 2, nullptr, AZ::ConsoleFunctorFlags::Null, "TaskGraph number of hardware threads that are reserved for O3DE system threads. Value is clamped between 0 and the number of logical cores in the system");
|
||||
AZ_CVAR(uint32_t, cl_taskGraphThreadsMinNumber, 2, nullptr, AZ::ConsoleFunctorFlags::Null, "TaskGraph minimum number of worker threads to create after scaling the number of hw threads");
|
||||
|
||||
static constexpr uint32_t TaskExecutorServiceCrc = AZ_CRC_CE("TaskExecutorService");
|
||||
|
||||
namespace AZ
|
||||
@@ -24,8 +30,8 @@ namespace AZ
|
||||
|
||||
if (Interface<TaskGraphActiveInterface>::Get() == nullptr)
|
||||
{
|
||||
Interface<TaskGraphActiveInterface>::Register(this);
|
||||
m_taskExecutor = aznew TaskExecutor();
|
||||
Interface<TaskGraphActiveInterface>::Register(this); // small window that another thread can try to use taskgraph between this line and the set instance.
|
||||
m_taskExecutor = aznew TaskExecutor(Threading::CalcNumWorkerThreads(cl_taskGraphThreadsConcurrencyRatio, cl_taskGraphThreadsMinNumber, cl_taskGraphThreadsNumReserved));
|
||||
TaskExecutor::SetInstance(m_taskExecutor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Threading/ThreadUtils.h>
|
||||
#include <AzCore/std/parallel/thread.h>
|
||||
#include <AzCore/Math/MathUtils.h>
|
||||
|
||||
namespace AZ::Threading
|
||||
{
|
||||
uint32_t CalcNumWorkerThreads(float workerThreadsRatio, uint32_t minNumWorkerThreads, uint32_t reservedNumThreads)
|
||||
{
|
||||
const uint32_t maxHardwareThreads = AZStd::thread::hardware_concurrency();
|
||||
const uint32_t numReservedThreads = AZ::GetMin<uint32_t>(reservedNumThreads, maxHardwareThreads); // protect against num reserved being bigger than the number of hw threads
|
||||
const uint32_t maxWorkerThreads = maxHardwareThreads - numReservedThreads;
|
||||
const float requestedWorkerThreads = AZ::GetClamp<float>(workerThreadsRatio, 0.0f, 1.0f) * static_cast<float>(maxWorkerThreads);
|
||||
const uint32_t requestedWorkerThreadsRounded = AZStd::lround(requestedWorkerThreads);
|
||||
const uint32_t numWorkerThreads = AZ::GetMax<uint32_t>(minNumWorkerThreads, requestedWorkerThreadsRounded);
|
||||
return numWorkerThreads;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/base.h>
|
||||
|
||||
namespace AZ::Threading
|
||||
{
|
||||
//! Calculates the number of worker threads a system should use based on the number of hardware threads a device has.
|
||||
//! result = max (minNumWorkerThreads, workerThreadsRatio * (num_hardware_threads - reservedNumThreads))
|
||||
//! @param workerThreadsRatio scale applied to the calculated maximum number of threads available after reserved threads have been accounted for. Clamped between 0 and 1.
|
||||
//! @param minNumWorkerThreads minimum value that will be returned. Value is unclamped and can be more than num_hardware_threads.
|
||||
//! @param reservedNumThreads number of hardware threads to reserve for O3DE system threads. Value clamped to num_hardware_threads.
|
||||
//! @return number of worker threads for the calling system to allocate
|
||||
uint32_t CalcNumWorkerThreads(float workerThreadsRatio, uint32_t minNumWorkerThreads, uint32_t reservedNumThreads);
|
||||
};
|
||||
@@ -566,6 +566,8 @@ set(FILES
|
||||
Settings/SettingsRegistryMergeUtils.h
|
||||
Settings/SettingsRegistryScriptUtils.cpp
|
||||
Settings/SettingsRegistryScriptUtils.h
|
||||
Settings/SettingsRegistryVisitorUtils.cpp
|
||||
Settings/SettingsRegistryVisitorUtils.h
|
||||
State/HSM.cpp
|
||||
State/HSM.h
|
||||
Statistics/NamedRunningStatistic.h
|
||||
@@ -639,6 +641,8 @@ set(FILES
|
||||
Threading/ThreadSafeDeque.inl
|
||||
Threading/ThreadSafeObject.h
|
||||
Threading/ThreadSafeObject.inl
|
||||
Threading/ThreadUtils.h
|
||||
Threading/ThreadUtils.cpp
|
||||
Time/ITime.h
|
||||
Time/TimeSystemComponent.cpp
|
||||
Time/TimeSystemComponent.h
|
||||
|
||||
+4
@@ -59,6 +59,10 @@ namespace AZStd
|
||||
{
|
||||
priority = desc->m_priority;
|
||||
}
|
||||
else
|
||||
{
|
||||
priority = SCHED_OTHER;
|
||||
}
|
||||
if (desc->m_name)
|
||||
{
|
||||
name = desc->m_name;
|
||||
|
||||
@@ -2088,7 +2088,7 @@ namespace UnitTest
|
||||
DisconnectNextHandlerByIdImpl multiHandler2;
|
||||
multiHandler2.BusConnect(DisconnectNextHandlerByIdImpl::firstBusAddress);
|
||||
multiHandler2.BusConnect(DisconnectNextHandlerByIdImpl::secondBusAddress);
|
||||
|
||||
|
||||
// Set the first handler m_nextHandler field to point to the second handler
|
||||
multiHandler1.m_nextHandler = &multiHandler2;
|
||||
|
||||
@@ -2807,7 +2807,7 @@ namespace UnitTest
|
||||
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(m_val % m_maxSleep));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DoConnect() override
|
||||
{
|
||||
MyEventGroupBus::Handler::BusConnect(m_id);
|
||||
@@ -2854,7 +2854,7 @@ namespace UnitTest
|
||||
}
|
||||
|
||||
MyEventGroupBus::Event(id, &MyEventGroupBus::Events::Calculate, i, i * 2, i << 4);
|
||||
|
||||
|
||||
LocklessConnectorBus::Event(id, &LocklessConnectorBus::Events::DoDisconnect);
|
||||
|
||||
bool failed = (AZStd::find_if(&sentinel[0], end, [](char s) { return s != 0; }) != end);
|
||||
@@ -2891,7 +2891,7 @@ namespace UnitTest
|
||||
{
|
||||
MyEventGroupImpl()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
~MyEventGroupImpl() override
|
||||
@@ -3614,7 +3614,7 @@ namespace UnitTest
|
||||
{
|
||||
AZStd::this_thread::yield();
|
||||
}
|
||||
|
||||
|
||||
EXPECT_GE(AZStd::chrono::system_clock::now(), endTime);
|
||||
};
|
||||
AZStd::thread connectThread([&connectHandler, &waitHandler]()
|
||||
@@ -3813,7 +3813,7 @@ namespace UnitTest
|
||||
struct LastHandlerDisconnectHandler
|
||||
: public LastHandlerDisconnectBus::Handler
|
||||
{
|
||||
void OnEvent() override
|
||||
void OnEvent() override
|
||||
{
|
||||
++m_numOnEvents;
|
||||
BusDisconnect();
|
||||
@@ -3854,7 +3854,7 @@ namespace UnitTest
|
||||
struct DisconnectAssertHandler
|
||||
: public DisconnectAssertBus::Handler
|
||||
{
|
||||
|
||||
|
||||
};
|
||||
|
||||
TEST_F(EBus, HandlerDestroyedWithoutDisconnect_Asserts)
|
||||
@@ -3995,6 +3995,191 @@ namespace UnitTest
|
||||
|
||||
idTestRequest.Disconnect();
|
||||
}
|
||||
|
||||
// IsInDispatchThisThread
|
||||
struct IsInThreadDispatchRequests
|
||||
: AZ::EBusTraits
|
||||
{
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
};
|
||||
|
||||
using IsInThreadDispatchBus = AZ::EBus<IsInThreadDispatchRequests>;
|
||||
|
||||
class IsInThreadDispatchHandler
|
||||
: public IsInThreadDispatchBus::Handler
|
||||
{};
|
||||
|
||||
TEST_F(EBus, InvokingIsInThisThread_ReturnsSuccess_OnlyIfThreadIsInDispatch)
|
||||
{
|
||||
IsInThreadDispatchHandler handler;
|
||||
handler.BusConnect();
|
||||
|
||||
auto ThreadDispatcher = [](IsInThreadDispatchRequests*)
|
||||
{
|
||||
EXPECT_TRUE(IsInThreadDispatchBus::IsInDispatchThisThread());
|
||||
auto PerThreadBusDispatch = []()
|
||||
{
|
||||
EXPECT_FALSE(IsInThreadDispatchBus::IsInDispatchThisThread());
|
||||
};
|
||||
AZStd::array threads{ AZStd::thread(PerThreadBusDispatch), AZStd::thread(PerThreadBusDispatch) };
|
||||
for (AZStd::thread& thread : threads)
|
||||
{
|
||||
thread.join();
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr size_t ThreadDispatcherIterations = 4;
|
||||
for (size_t iteration = 0; iteration < ThreadDispatcherIterations; ++iteration)
|
||||
{
|
||||
EXPECT_FALSE(IsInThreadDispatchBus::IsInDispatchThisThread());
|
||||
IsInThreadDispatchBus::Broadcast(ThreadDispatcher);
|
||||
EXPECT_FALSE(IsInThreadDispatchBus::IsInDispatchThisThread());
|
||||
}
|
||||
}
|
||||
|
||||
// Thread Dispatch Policy
|
||||
struct ThreadDispatchTestBusTraits
|
||||
: AZ::EBusTraits
|
||||
{
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
|
||||
struct PostThreadDispatchTestInvoker
|
||||
{
|
||||
~PostThreadDispatchTestInvoker();
|
||||
};
|
||||
|
||||
template <typename DispatchMutex>
|
||||
struct ThreadDispatchTestLockGuard
|
||||
{
|
||||
ThreadDispatchTestLockGuard(DispatchMutex& contextMutex)
|
||||
: m_lock{ contextMutex }
|
||||
{}
|
||||
ThreadDispatchTestLockGuard(DispatchMutex& contextMutex, AZStd::adopt_lock_t adopt_lock)
|
||||
: m_lock{ contextMutex, adopt_lock }
|
||||
{}
|
||||
ThreadDispatchTestLockGuard(const ThreadDispatchTestLockGuard&) = delete;
|
||||
ThreadDispatchTestLockGuard& operator=(const ThreadDispatchTestLockGuard&) = delete;
|
||||
private:
|
||||
PostThreadDispatchTestInvoker m_threadPolicyInvoker;
|
||||
using LockType = AZStd::conditional_t<LocklessDispatch, AZ::Internal::NullLockGuard<DispatchMutex>, AZStd::scoped_lock<DispatchMutex>>;
|
||||
LockType m_lock;
|
||||
};
|
||||
|
||||
template <typename DispatchMutex, bool IsLocklessDispatch>
|
||||
using DispatchLockGuard = ThreadDispatchTestLockGuard<DispatchMutex>;
|
||||
|
||||
static inline AZStd::atomic<int32_t> s_threadPostDispatchCalls;
|
||||
};
|
||||
|
||||
class ThreadDispatchTestRequests
|
||||
{
|
||||
public:
|
||||
virtual void FirstCall() = 0;
|
||||
virtual void SecondCall() = 0;
|
||||
virtual void ThirdCall() = 0;
|
||||
};
|
||||
|
||||
using ThreadDispatchTestBus = AZ::EBus<ThreadDispatchTestRequests, ThreadDispatchTestBusTraits>;
|
||||
|
||||
ThreadDispatchTestBusTraits::PostThreadDispatchTestInvoker::~PostThreadDispatchTestInvoker()
|
||||
{
|
||||
if (!ThreadDispatchTestBus::IsInDispatchThisThread())
|
||||
{
|
||||
++s_threadPostDispatchCalls;
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadDispatchTestHandler
|
||||
: public ThreadDispatchTestBus::Handler
|
||||
{
|
||||
public:
|
||||
void Connect()
|
||||
{
|
||||
ThreadDispatchTestBus::Handler::BusConnect();
|
||||
}
|
||||
void Disconnect()
|
||||
{
|
||||
ThreadDispatchTestBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
void FirstCall() override
|
||||
{
|
||||
ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::SecondCall);
|
||||
}
|
||||
void SecondCall() override
|
||||
{
|
||||
ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::ThirdCall);
|
||||
}
|
||||
void ThirdCall() override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ParamType>
|
||||
class EBusParamFixture
|
||||
: public ScopedAllocatorSetupFixture
|
||||
, public ::testing::WithParamInterface<ParamType>
|
||||
{};
|
||||
|
||||
struct ThreadDispatchParams
|
||||
{
|
||||
size_t m_threadCount{};
|
||||
size_t m_handlerCount{};
|
||||
};
|
||||
|
||||
using ThreadDispatchParamFixture = EBusParamFixture<ThreadDispatchParams>;
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ThreadDispatch,
|
||||
ThreadDispatchParamFixture,
|
||||
::testing::Values(
|
||||
ThreadDispatchParams{ 1, 1 },
|
||||
ThreadDispatchParams{ 2, 1 },
|
||||
ThreadDispatchParams{ 1, 2 },
|
||||
ThreadDispatchParams{ 2, 2 },
|
||||
ThreadDispatchParams{ 16, 8 }
|
||||
)
|
||||
);
|
||||
|
||||
TEST_P(ThreadDispatchParamFixture, CustomDispatchLockGuard_InvokesPostDispatchFunction_AfterThreadHasFinishedDispatch)
|
||||
{
|
||||
ThreadDispatchTestBusTraits::s_threadPostDispatchCalls = 0;
|
||||
ThreadDispatchParams threadDispatchParams = GetParam();
|
||||
AZStd::vector<AZStd::thread> testThreads;
|
||||
AZStd::vector<ThreadDispatchTestHandler> testHandlers(threadDispatchParams.m_handlerCount);
|
||||
for (ThreadDispatchTestHandler& testHandler : testHandlers)
|
||||
{
|
||||
testHandler.Connect();
|
||||
}
|
||||
|
||||
static constexpr size_t DispatchThreadCalls = 3;
|
||||
const size_t totalThreadDispatchCalls = threadDispatchParams.m_threadCount * DispatchThreadCalls;
|
||||
|
||||
auto DispatchThreadWorker = []()
|
||||
{
|
||||
ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::FirstCall);
|
||||
ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::SecondCall);
|
||||
ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::ThirdCall);
|
||||
};
|
||||
|
||||
for (size_t threadIndex = 0; threadIndex < threadDispatchParams.m_threadCount; ++threadIndex)
|
||||
{
|
||||
testThreads.emplace_back(DispatchThreadWorker);
|
||||
}
|
||||
|
||||
for (AZStd::thread& thread : testThreads)
|
||||
{
|
||||
thread.join();
|
||||
}
|
||||
|
||||
for (ThreadDispatchTestHandler& testHandler : testHandlers)
|
||||
{
|
||||
testHandler.Disconnect();
|
||||
}
|
||||
|
||||
EXPECT_EQ(totalThreadDispatchCalls, ThreadDispatchTestBusTraits::s_threadPostDispatchCalls);
|
||||
ThreadDispatchTestBusTraits::s_threadPostDispatchCalls = 0;
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
#if defined(HAVE_BENCHMARK)
|
||||
@@ -4370,7 +4555,7 @@ namespace Benchmark
|
||||
Bus::ExecuteQueuedEvents();
|
||||
}
|
||||
s_benchmarkEBusEnv<Bus>.Disconnect(state);
|
||||
|
||||
|
||||
}
|
||||
BUS_BENCHMARK_REGISTER_ALL(BM_EBus_ExecuteBroadcast);
|
||||
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <AzCore/Settings/SettingsRegistryImpl.h>
|
||||
#include <AzCore/Settings/SettingsRegistryVisitorUtils.h>
|
||||
#include <AzCore/std/containers/fixed_vector.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
|
||||
namespace SettingsRegistryVisitorUtilsTests
|
||||
{
|
||||
struct VisitCallbackParams
|
||||
{
|
||||
AZStd::string_view m_inputJsonDocument;
|
||||
using VisitFieldFunction = bool(*)(AZ::SettingsRegistryInterface&,
|
||||
const AZ::SettingsRegistryVisitorUtils::VisitorCallback&,
|
||||
AZStd::string_view);
|
||||
|
||||
static inline constexpr size_t MaxFieldCount = 10;
|
||||
using ObjectFields = AZStd::fixed_vector<AZStd::pair<AZStd::string_view, AZStd::string_view>, MaxFieldCount>;
|
||||
using ArrayFields = AZStd::fixed_vector<AZStd::string_view, MaxFieldCount>;
|
||||
ObjectFields m_objectFields;
|
||||
ArrayFields m_arrayFields;
|
||||
};
|
||||
|
||||
template <typename VisitorParams>
|
||||
class SettingsRegistryVisitorUtilsParamFixture
|
||||
: public UnitTest::ScopedAllocatorSetupFixture
|
||||
, public ::testing::WithParamInterface<VisitorParams>
|
||||
{
|
||||
public:
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
m_registry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_registry.reset();
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::SettingsRegistryImpl> m_registry;
|
||||
};
|
||||
|
||||
using SettingsRegistryVisitCallbackFixture = SettingsRegistryVisitorUtilsParamFixture<VisitCallbackParams>;
|
||||
|
||||
TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitFieldsOfArrayType_ReturnsFields)
|
||||
{
|
||||
const VisitCallbackParams& visitParams = GetParam();
|
||||
|
||||
ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch));
|
||||
|
||||
AZStd::fixed_vector<AZStd::string, VisitCallbackParams::MaxFieldCount> testArrayFields;
|
||||
auto visitorCallback = [this, &testArrayFields](AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
AZStd::string fieldValue;
|
||||
EXPECT_TRUE(m_registry->Get(fieldValue, path));
|
||||
testArrayFields.emplace_back(AZStd::move(fieldValue));
|
||||
};
|
||||
|
||||
AZ::SettingsRegistryVisitorUtils::VisitField(*m_registry, visitorCallback, "/Test/Array");
|
||||
|
||||
const AZStd::fixed_vector<AZStd::string, VisitCallbackParams::MaxFieldCount> expectedFields{
|
||||
visitParams.m_arrayFields.begin(), visitParams.m_arrayFields.end() };
|
||||
EXPECT_THAT(testArrayFields, ::testing::ContainerEq(expectedFields));
|
||||
}
|
||||
|
||||
TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitFieldsOfObjectType_ReturnsFields)
|
||||
{
|
||||
const VisitCallbackParams& visitParams = GetParam();
|
||||
|
||||
ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch));
|
||||
|
||||
AZStd::fixed_vector<AZStd::pair<AZStd::string, AZStd::string>, VisitCallbackParams::MaxFieldCount> testObjectFields;
|
||||
auto visitorCallback = [this, &testObjectFields](AZStd::string_view path, AZStd::string_view fieldName, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
AZStd::string fieldValue;
|
||||
EXPECT_TRUE(m_registry->Get(fieldValue, path));
|
||||
testObjectFields.emplace_back(fieldName, AZStd::move(fieldValue));
|
||||
};
|
||||
|
||||
AZ::SettingsRegistryVisitorUtils::VisitField(*m_registry, visitorCallback, "/Test/Object");
|
||||
|
||||
const AZStd::fixed_vector<AZStd::pair<AZStd::string, AZStd::string>, VisitCallbackParams::MaxFieldCount> expectedFields{
|
||||
visitParams.m_objectFields.begin(), visitParams.m_objectFields.end() };
|
||||
EXPECT_THAT(testObjectFields, ::testing::ContainerEq(expectedFields));
|
||||
}
|
||||
|
||||
TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitArrayOfArrayType_ReturnsFields)
|
||||
{
|
||||
const VisitCallbackParams& visitParams = GetParam();
|
||||
|
||||
ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch));
|
||||
|
||||
AZStd::fixed_vector<AZStd::string, VisitCallbackParams::MaxFieldCount> testArrayFields;
|
||||
auto visitorCallback = [this, &testArrayFields](AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
AZStd::string fieldValue;
|
||||
EXPECT_TRUE(m_registry->Get(fieldValue, path));
|
||||
testArrayFields.emplace_back(AZStd::move(fieldValue));
|
||||
};
|
||||
|
||||
AZ::SettingsRegistryVisitorUtils::VisitArray(*m_registry, visitorCallback, "/Test/Array");
|
||||
|
||||
const AZStd::fixed_vector<AZStd::string, VisitCallbackParams::MaxFieldCount> expectedArrayFields{
|
||||
visitParams.m_arrayFields.begin(), visitParams.m_arrayFields.end() };
|
||||
EXPECT_THAT(testArrayFields, ::testing::ContainerEq(expectedArrayFields));
|
||||
}
|
||||
|
||||
TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitArrayOfObjectType_ReturnsEmpty)
|
||||
{
|
||||
const VisitCallbackParams& visitParams = GetParam();
|
||||
|
||||
ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch));
|
||||
|
||||
AZStd::fixed_vector<AZStd::string, VisitCallbackParams::MaxFieldCount> testArrayFields;
|
||||
auto visitorCallback = [this, &testArrayFields](AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
AZStd::string fieldValue;
|
||||
EXPECT_TRUE(m_registry->Get(fieldValue, path));
|
||||
testArrayFields.emplace_back(AZStd::move(fieldValue));
|
||||
};
|
||||
|
||||
AZ::SettingsRegistryVisitorUtils::VisitArray(*m_registry, visitorCallback, "/Test/Object");
|
||||
|
||||
EXPECT_TRUE(testArrayFields.empty());
|
||||
}
|
||||
|
||||
TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitObjectOfArrayType_ReturnsEmpty)
|
||||
{
|
||||
const VisitCallbackParams& visitParams = GetParam();
|
||||
|
||||
ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch));
|
||||
|
||||
AZStd::fixed_vector<AZStd::pair<AZStd::string, AZStd::string>, VisitCallbackParams::MaxFieldCount> testObjectFields;
|
||||
auto visitorCallback = [this, &testObjectFields](AZStd::string_view path, AZStd::string_view fieldName, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
AZStd::string fieldValue;
|
||||
EXPECT_TRUE(m_registry->Get(fieldValue, path));
|
||||
testObjectFields.emplace_back(fieldName, AZStd::move(fieldValue));
|
||||
};
|
||||
|
||||
AZ::SettingsRegistryVisitorUtils::VisitObject(*m_registry, visitorCallback, "/Test/Array");
|
||||
|
||||
EXPECT_TRUE(testObjectFields.empty());
|
||||
}
|
||||
|
||||
TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitObjectOfObjectType_ReturnsFields)
|
||||
{
|
||||
const VisitCallbackParams& visitParams = GetParam();
|
||||
|
||||
ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch));
|
||||
|
||||
AZStd::fixed_vector<AZStd::pair<AZStd::string, AZStd::string>, VisitCallbackParams::MaxFieldCount> testObjectFields;
|
||||
auto visitorCallback = [this, &testObjectFields](AZStd::string_view path, AZStd::string_view fieldName, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
AZStd::string fieldValue;
|
||||
EXPECT_TRUE(m_registry->Get(fieldValue, path));
|
||||
testObjectFields.emplace_back(fieldName, AZStd::move(fieldValue));
|
||||
};
|
||||
|
||||
AZ::SettingsRegistryVisitorUtils::VisitObject(*m_registry, visitorCallback, "/Test/Object");
|
||||
|
||||
const AZStd::fixed_vector<AZStd::pair<AZStd::string, AZStd::string>, VisitCallbackParams::MaxFieldCount> expectedObjectFields{
|
||||
visitParams.m_objectFields.begin(), visitParams.m_objectFields.end() };
|
||||
EXPECT_THAT(testObjectFields, ::testing::ContainerEq(expectedObjectFields));
|
||||
}
|
||||
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
VisitField,
|
||||
SettingsRegistryVisitCallbackFixture,
|
||||
::testing::Values(
|
||||
VisitCallbackParams
|
||||
{
|
||||
R"({)" "\n"
|
||||
R"( "Test":)" "\n"
|
||||
R"( {)" "\n"
|
||||
R"( "Array": [ "Hello", "World" ],)" "\n"
|
||||
R"( "Object": { "Foo": "Hello", "Bar": "World"})" "\n"
|
||||
R"( })" "\n"
|
||||
R"(})" "\n",
|
||||
VisitCallbackParams::ObjectFields{{"Foo", "Hello"}, {"Bar", "World"}},
|
||||
VisitCallbackParams::ArrayFields{"Hello", "World"}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -247,4 +247,27 @@ namespace UnitTest
|
||||
Uuid right = Uuid::CreateStringPermissive(permissiveStr1);
|
||||
EXPECT_EQ(left, right);
|
||||
}
|
||||
|
||||
TEST_F(UuidTests, CreateStringPermissive_StringWithExtraData_Succeeds)
|
||||
{
|
||||
const char uuidStr[] = "{34D44249-E599-4B30-811F-4215C2DEA269}";
|
||||
Uuid left = Uuid::CreateString(uuidStr);
|
||||
|
||||
const char permissiveStr[] = "0x34D44249-0xE5994B30-0x811F4215-0xC2DEA269 Hello World";
|
||||
Uuid right = Uuid::CreateStringPermissive(permissiveStr);
|
||||
EXPECT_EQ(left, right);
|
||||
|
||||
}
|
||||
|
||||
TEST_F(UuidTests, CreateStringPermissive_StringWithLotsOfExtraData_Succeeds)
|
||||
{
|
||||
const char uuidStr[] = "{34D44249-E599-4B30-811F-4215C2DEA269}";
|
||||
Uuid left = Uuid::CreateString(uuidStr);
|
||||
|
||||
const char permissiveStr[] = "0x34D44249-0xE5994B30-0x811F4215-0xC2DEA269 Hello World this is a really long string "
|
||||
"with lots of extra data to make sure we can parse a long string without failing as long as the uuid is in "
|
||||
"the beginning of the string then we should succeed";
|
||||
Uuid right = Uuid::CreateStringPermissive(permissiveStr);
|
||||
EXPECT_EQ(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,11 +75,12 @@ set(FILES
|
||||
Name/NameJsonSerializerTests.cpp
|
||||
Name/NameTests.cpp
|
||||
RTTI/TypeSafeIntegralTests.cpp
|
||||
SettingsRegistryTests.cpp
|
||||
SettingsRegistryMergeUtilsTests.cpp
|
||||
Settings/CommandLineTests.cpp
|
||||
Settings/SettingsRegistryTests.cpp
|
||||
Settings/SettingsRegistryConsoleUtilsTests.cpp
|
||||
Settings/SettingsRegistryMergeUtilsTests.cpp
|
||||
Settings/SettingsRegistryScriptUtilsTests.cpp
|
||||
Settings/SettingsRegistryVisitorUtilsTests.cpp
|
||||
Streamer/BlockCacheTests.cpp
|
||||
Streamer/DedicatedCacheTests.cpp
|
||||
Streamer/FullDecompressorTests.cpp
|
||||
|
||||
@@ -133,6 +133,8 @@ namespace AzFramework
|
||||
behaviorContext->Class<BehaviorEntity>("Entity")
|
||||
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
||||
->Attribute(AZ::Script::Attributes::ConstructorOverride, &Internal::BehaviorEntityScriptConstructor)
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "entity")
|
||||
->Constructor()
|
||||
->Constructor<AZ::EntityId>()
|
||||
->Constructor<AZ::Entity*>()
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzFramework/Matchmaking/MatchmakingRequests.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
//! IMatchmakingRequests
|
||||
//! Pure virtual session interface class to abstract the details of session handling from application code.
|
||||
class IMatchmakingRequests
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(IMatchmakingRequests, "{BC0B74DA-A448-4F40-9B50-9D73142829D5}");
|
||||
|
||||
IMatchmakingRequests() = default;
|
||||
virtual ~IMatchmakingRequests() = default;
|
||||
|
||||
// Registers a player's acceptance or rejection of a proposed matchmaking.
|
||||
// @param acceptMatchRequest The request of AcceptMatch operation
|
||||
virtual void AcceptMatch(const AcceptMatchRequest& acceptMatchRequest) = 0;
|
||||
|
||||
// Create a game match for a group of players.
|
||||
// @param startMatchmakingRequest The request of StartMatchmaking operation
|
||||
// @return A unique identifier for a matchmaking ticket
|
||||
virtual AZStd::string StartMatchmaking(const StartMatchmakingRequest& startMatchmakingRequest) = 0;
|
||||
|
||||
// Cancels a matchmaking ticket that is currently being processed.
|
||||
// @param stopMatchmakingRequest The request of StopMatchmaking operation
|
||||
virtual void StopMatchmaking(const StopMatchmakingRequest& stopMatchmakingRequest) = 0;
|
||||
};
|
||||
|
||||
//! IMatchmakingAsyncRequests
|
||||
//! Async version of IMatchmakingRequests
|
||||
class IMatchmakingAsyncRequests
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(ISessionAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}");
|
||||
|
||||
IMatchmakingAsyncRequests() = default;
|
||||
virtual ~IMatchmakingAsyncRequests() = default;
|
||||
|
||||
// AcceptMatch Async
|
||||
// @param acceptMatchRequest The request of AcceptMatch operation
|
||||
virtual void AcceptMatchAsync(const AcceptMatchRequest& acceptMatchRequest) = 0;
|
||||
|
||||
// StartMatchmaking Async
|
||||
// @param startMatchmakingRequest The request of StartMatchmaking operation
|
||||
virtual void StartMatchmakingAsync(const StartMatchmakingRequest& startMatchmakingRequest) = 0;
|
||||
|
||||
// StopMatchmaking Async
|
||||
// @param stopMatchmakingRequest The request of StopMatchmaking operation
|
||||
virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
//! MatchmakingAsyncRequestNotifications
|
||||
//! The notifications correspond to matchmaking async requests
|
||||
class MatchmakingAsyncRequestNotifications
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
// Safeguard handler for multi-threaded use case
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes
|
||||
virtual void OnAcceptMatchAsyncComplete() = 0;
|
||||
|
||||
// OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes
|
||||
// @param matchmakingTicketId The unique identifier for the matchmaking ticket
|
||||
virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0;
|
||||
|
||||
// OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes
|
||||
virtual void OnStopMatchmakingAsyncComplete() = 0;
|
||||
};
|
||||
using MatchmakingAsyncRequestNotificationBus = AZ::EBus<MatchmakingAsyncRequestNotifications>;
|
||||
|
||||
//! MatchmakingNotifications
|
||||
//! The matchmaking notifications to listen for performing required operations
|
||||
class MatchAcceptanceNotifications
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
// Safeguard handler for multi-threaded use case
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// OnMatchAcceptance is fired when DescribeMatchmaking ticket status is REQUIRES_ACCEPTANCE
|
||||
virtual void OnMatchAcceptance() = 0;
|
||||
};
|
||||
using MatchAcceptanceNotificationBus = AZ::EBus<MatchAcceptanceNotifications>;
|
||||
} // namespace AzFramework
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzFramework/Matchmaking/MatchmakingRequests.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
void AcceptMatchRequest::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<AcceptMatchRequest>()
|
||||
->Version(0)
|
||||
->Field("acceptMatch", &AcceptMatchRequest::m_acceptMatch)
|
||||
->Field("playerIds", &AcceptMatchRequest::m_playerIds)
|
||||
->Field("ticketId", &AcceptMatchRequest::m_ticketId);
|
||||
|
||||
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
|
||||
{
|
||||
editContext->Class<AcceptMatchRequest>("AcceptMatchRequest", "The container for AcceptMatch request parameters")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &AcceptMatchRequest::m_acceptMatch, "AcceptMatch",
|
||||
"Player response to accept or reject match")
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &AcceptMatchRequest::m_playerIds, "PlayerIds",
|
||||
"A list of unique identifiers for players delivering the response")
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &AcceptMatchRequest::m_ticketId, "TicketId",
|
||||
"A unique identifier for a matchmaking ticket");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StartMatchmakingRequest::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<StartMatchmakingRequest>()
|
||||
->Version(0)
|
||||
->Field("ticketId", &StartMatchmakingRequest::m_ticketId);
|
||||
|
||||
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
|
||||
{
|
||||
editContext->Class<StartMatchmakingRequest>("StartMatchmakingRequest", "The container for StartMatchmaking request parameters")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &StartMatchmakingRequest::m_ticketId, "TicketId",
|
||||
"A unique identifier for a matchmaking ticket");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StopMatchmakingRequest::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<StopMatchmakingRequest>()
|
||||
->Version(0)
|
||||
->Field("ticketId", &StopMatchmakingRequest::m_ticketId);
|
||||
|
||||
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
|
||||
{
|
||||
editContext->Class<StopMatchmakingRequest>("StopMatchmakingRequest", "The container for StopMatchmaking request parameters")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &StopMatchmakingRequest::m_ticketId, "TicketId",
|
||||
"A unique identifier for a matchmaking ticket");
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class ReflectContext;
|
||||
}
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
//! AcceptMatchRequest
|
||||
//! The container for AcceptMatch request parameters.
|
||||
struct AcceptMatchRequest
|
||||
{
|
||||
AZ_RTTI(AcceptMatchRequest, "{AD289D76-CEE2-424F-847E-E62AA83B7D79}");
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
AcceptMatchRequest() = default;
|
||||
virtual ~AcceptMatchRequest() = default;
|
||||
|
||||
// Player response to accept or reject match
|
||||
bool m_acceptMatch;
|
||||
// A list of unique identifiers for players delivering the response
|
||||
AZStd::vector<AZStd::string> m_playerIds;
|
||||
// A unique identifier for a matchmaking ticket
|
||||
AZStd::string m_ticketId;
|
||||
};
|
||||
|
||||
//! StartMatchmakingRequest
|
||||
//! The container for StartMatchmaking request parameters.
|
||||
struct StartMatchmakingRequest
|
||||
{
|
||||
AZ_RTTI(StartMatchmakingRequest, "{70B47776-E8E7-4993-BEC3-5CAEC3D48E47}");
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
StartMatchmakingRequest() = default;
|
||||
virtual ~StartMatchmakingRequest() = default;
|
||||
|
||||
// A unique identifier for a matchmaking ticket
|
||||
AZStd::string m_ticketId;
|
||||
};
|
||||
|
||||
//! StopMatchmakingRequest
|
||||
//! The container for StopMatchmaking request parameters.
|
||||
struct StopMatchmakingRequest
|
||||
{
|
||||
AZ_RTTI(StopMatchmakingRequest, "{6132E293-65EF-4DC2-A8A0-00269697229D}");
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
StopMatchmakingRequest() = default;
|
||||
virtual ~StopMatchmakingRequest() = default;
|
||||
|
||||
// A unique identifier for a matchmaking ticket
|
||||
AZStd::string m_ticketId;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
@@ -10,98 +10,11 @@
|
||||
|
||||
#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>
|
||||
#include <AzFramework/Session/SessionRequests.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 = 0;
|
||||
};
|
||||
|
||||
//! 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 = 0;
|
||||
|
||||
// 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
|
||||
|
||||
+2
-1
@@ -6,9 +6,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzFramework/Session/ISessionRequests.h>
|
||||
#include <AzFramework/Session/SessionRequests.h>
|
||||
#include <AzFramework/Session/SessionConfig.h>
|
||||
|
||||
namespace AzFramework
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <AzCore/std/containers/unordered_map.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class ReflectContext;
|
||||
}
|
||||
|
||||
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 = 0;
|
||||
};
|
||||
|
||||
//! 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 = 0;
|
||||
|
||||
// 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;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
@@ -533,8 +533,8 @@ namespace AzFramework
|
||||
m_translateCameraInputChannelIds = translateCameraInputChannelIds;
|
||||
}
|
||||
|
||||
PivotCameraInput::PivotCameraInput(const InputChannelId& pivotChannelId)
|
||||
: m_pivotChannelId(pivotChannelId)
|
||||
OrbitCameraInput::OrbitCameraInput(const InputChannelId& orbitChannelId)
|
||||
: m_orbitChannelId(orbitChannelId)
|
||||
{
|
||||
m_pivotFn = []([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction)
|
||||
{
|
||||
@@ -542,11 +542,11 @@ namespace AzFramework
|
||||
};
|
||||
}
|
||||
|
||||
bool PivotCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta)
|
||||
bool OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta)
|
||||
{
|
||||
if (const auto* input = AZStd::get_if<DiscreteInputEvent>(&event))
|
||||
{
|
||||
if (input->m_channelId == m_pivotChannelId)
|
||||
if (input->m_channelId == m_orbitChannelId)
|
||||
{
|
||||
if (input->m_state == InputChannel::State::Began)
|
||||
{
|
||||
@@ -561,13 +561,13 @@ namespace AzFramework
|
||||
|
||||
if (Active())
|
||||
{
|
||||
return m_pivotCameras.HandleEvents(event, cursorDelta, scrollDelta);
|
||||
return m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta);
|
||||
}
|
||||
|
||||
return !Idle();
|
||||
}
|
||||
|
||||
Camera PivotCameraInput::StepCamera(
|
||||
Camera OrbitCameraInput::StepCamera(
|
||||
const Camera& targetCamera, const ScreenVector& cursorDelta, const float scrollDelta, const float deltaTime)
|
||||
{
|
||||
Camera nextCamera = targetCamera;
|
||||
@@ -581,12 +581,12 @@ namespace AzFramework
|
||||
if (Active())
|
||||
{
|
||||
MovePivotDetached(nextCamera, m_pivotFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY()));
|
||||
nextCamera = m_pivotCameras.StepCamera(nextCamera, cursorDelta, scrollDelta, deltaTime);
|
||||
nextCamera = m_orbitCameras.StepCamera(nextCamera, cursorDelta, scrollDelta, deltaTime);
|
||||
}
|
||||
|
||||
if (Ending())
|
||||
{
|
||||
m_pivotCameras.Reset();
|
||||
m_orbitCameras.Reset();
|
||||
|
||||
nextCamera.m_pivot = nextCamera.Translation();
|
||||
nextCamera.m_offset = AZ::Vector3::CreateZero();
|
||||
@@ -595,12 +595,12 @@ namespace AzFramework
|
||||
return nextCamera;
|
||||
}
|
||||
|
||||
void PivotCameraInput::SetPivotInputChannelId(const InputChannelId& pivotChanneId)
|
||||
void OrbitCameraInput::SetOrbitInputChannelId(const InputChannelId& orbitChanneId)
|
||||
{
|
||||
m_pivotChannelId = pivotChanneId;
|
||||
m_orbitChannelId = orbitChanneId;
|
||||
}
|
||||
|
||||
PivotDollyScrollCameraInput::PivotDollyScrollCameraInput()
|
||||
OrbitDollyScrollCameraInput::OrbitDollyScrollCameraInput()
|
||||
{
|
||||
m_scrollSpeedFn = []() constexpr
|
||||
{
|
||||
@@ -608,7 +608,7 @@ namespace AzFramework
|
||||
};
|
||||
}
|
||||
|
||||
bool PivotDollyScrollCameraInput::HandleEvents(
|
||||
bool OrbitDollyScrollCameraInput::HandleEvents(
|
||||
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta)
|
||||
{
|
||||
if (const auto* scroll = AZStd::get_if<ScrollEvent>(&event))
|
||||
@@ -619,36 +619,45 @@ namespace AzFramework
|
||||
return !Idle();
|
||||
}
|
||||
|
||||
static Camera PivotDolly(const Camera& targetCamera, const float delta)
|
||||
static Camera OrbitDolly(const Camera& targetCamera, const float delta)
|
||||
{
|
||||
Camera nextCamera = targetCamera;
|
||||
|
||||
const auto pivotDirection = targetCamera.m_offset.GetNormalized();
|
||||
nextCamera.m_offset -= pivotDirection * delta;
|
||||
const auto pivotDot = targetCamera.m_offset.Dot(nextCamera.m_offset);
|
||||
const auto distance = nextCamera.m_offset.GetLength() * AZ::GetSign(pivotDot);
|
||||
|
||||
const auto minDistance = 0.01f;
|
||||
if (distance < minDistance || pivotDot < 0.0f)
|
||||
// handle case where pivot and offset may be the same to begin with
|
||||
// choose negative y-axis for offset to default to moving the camera backwards from the pivot (standard centered pivot behavior)
|
||||
const auto pivotDirection = [&targetCamera]
|
||||
{
|
||||
nextCamera.m_offset = pivotDirection * minDistance;
|
||||
if (const auto offsetLength = targetCamera.m_offset.GetLength(); AZ::IsCloseMag(offsetLength, 0.0f))
|
||||
{
|
||||
return -AZ::Vector3::CreateAxisY();
|
||||
}
|
||||
else
|
||||
{
|
||||
return targetCamera.m_offset / offsetLength;
|
||||
}
|
||||
}();
|
||||
|
||||
nextCamera.m_offset -= pivotDirection * delta;
|
||||
if (pivotDirection.Dot(nextCamera.m_offset) < 0.0f)
|
||||
{
|
||||
nextCamera.m_offset = pivotDirection * 0.001f;
|
||||
}
|
||||
|
||||
return nextCamera;
|
||||
}
|
||||
|
||||
Camera PivotDollyScrollCameraInput::StepCamera(
|
||||
Camera OrbitDollyScrollCameraInput::StepCamera(
|
||||
const Camera& targetCamera,
|
||||
[[maybe_unused]] const ScreenVector& cursorDelta,
|
||||
const float scrollDelta,
|
||||
[[maybe_unused]] const float deltaTime)
|
||||
{
|
||||
const auto nextCamera = PivotDolly(targetCamera, aznumeric_cast<float>(scrollDelta) * m_scrollSpeedFn());
|
||||
const auto nextCamera = OrbitDolly(targetCamera, aznumeric_cast<float>(scrollDelta) * m_scrollSpeedFn());
|
||||
EndActivation();
|
||||
return nextCamera;
|
||||
}
|
||||
|
||||
PivotDollyMotionCameraInput::PivotDollyMotionCameraInput(const InputChannelId& dollyChannelId)
|
||||
OrbitDollyMotionCameraInput::OrbitDollyMotionCameraInput(const InputChannelId& dollyChannelId)
|
||||
: m_dollyChannelId(dollyChannelId)
|
||||
{
|
||||
m_motionSpeedFn = []() constexpr
|
||||
@@ -657,28 +666,28 @@ namespace AzFramework
|
||||
};
|
||||
}
|
||||
|
||||
bool PivotDollyMotionCameraInput::HandleEvents(
|
||||
bool OrbitDollyMotionCameraInput::HandleEvents(
|
||||
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta)
|
||||
{
|
||||
HandleActivationEvents(event, m_dollyChannelId, cursorDelta, m_clickDetector, *this);
|
||||
return CameraInputUpdatingAfterMotion(*this);
|
||||
}
|
||||
|
||||
Camera PivotDollyMotionCameraInput::StepCamera(
|
||||
Camera OrbitDollyMotionCameraInput::StepCamera(
|
||||
const Camera& targetCamera,
|
||||
const ScreenVector& cursorDelta,
|
||||
[[maybe_unused]] const float scrollDelta,
|
||||
[[maybe_unused]] const float deltaTime)
|
||||
{
|
||||
return PivotDolly(targetCamera, aznumeric_cast<float>(cursorDelta.m_y) * m_motionSpeedFn());
|
||||
return OrbitDolly(targetCamera, aznumeric_cast<float>(cursorDelta.m_y) * m_motionSpeedFn());
|
||||
}
|
||||
|
||||
void PivotDollyMotionCameraInput::SetDollyInputChannelId(const InputChannelId& dollyChannelId)
|
||||
void OrbitDollyMotionCameraInput::SetDollyInputChannelId(const InputChannelId& dollyChannelId)
|
||||
{
|
||||
m_dollyChannelId = dollyChannelId;
|
||||
}
|
||||
|
||||
ScrollTranslationCameraInput::ScrollTranslationCameraInput()
|
||||
LookScrollTranslationCameraInput::LookScrollTranslationCameraInput()
|
||||
{
|
||||
m_scrollSpeedFn = []() constexpr
|
||||
{
|
||||
@@ -686,7 +695,7 @@ namespace AzFramework
|
||||
};
|
||||
}
|
||||
|
||||
bool ScrollTranslationCameraInput::HandleEvents(
|
||||
bool LookScrollTranslationCameraInput::HandleEvents(
|
||||
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta)
|
||||
{
|
||||
if (const auto* scroll = AZStd::get_if<ScrollEvent>(&event))
|
||||
@@ -697,7 +706,7 @@ namespace AzFramework
|
||||
return !Idle();
|
||||
}
|
||||
|
||||
Camera ScrollTranslationCameraInput::StepCamera(
|
||||
Camera LookScrollTranslationCameraInput::StepCamera(
|
||||
const Camera& targetCamera,
|
||||
[[maybe_unused]] const ScreenVector& cursorDelta,
|
||||
const float scrollDelta,
|
||||
@@ -771,6 +780,73 @@ namespace AzFramework
|
||||
return camera;
|
||||
}
|
||||
|
||||
FocusCameraInput::FocusCameraInput(const InputChannelId& focusChannelId, FocusOffsetFn offsetFn)
|
||||
: m_focusChannelId(focusChannelId)
|
||||
, m_offsetFn(offsetFn)
|
||||
{
|
||||
}
|
||||
|
||||
bool FocusCameraInput::HandleEvents(
|
||||
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
|
||||
{
|
||||
if (const auto* input = AZStd::get_if<DiscreteInputEvent>(&event))
|
||||
{
|
||||
if (input->m_channelId == m_focusChannelId && input->m_state == InputChannel::State::Began)
|
||||
{
|
||||
BeginActivation();
|
||||
}
|
||||
}
|
||||
|
||||
return !Idle();
|
||||
}
|
||||
|
||||
Camera FocusCameraInput::StepCamera(
|
||||
const Camera& targetCamera,
|
||||
[[maybe_unused]] const ScreenVector& cursorDelta,
|
||||
[[maybe_unused]] float scrollDelta,
|
||||
[[maybe_unused]] float deltaTime)
|
||||
{
|
||||
if (Beginning())
|
||||
{
|
||||
// as the camera starts, record the camera we would like to end up as
|
||||
m_nextCamera.m_offset = m_offsetFn(m_pivotFn().GetDistance(targetCamera.Translation()));
|
||||
const auto angles =
|
||||
EulerAngles(AZ::Matrix3x3::CreateFromMatrix3x4(AZ::Matrix3x4::CreateLookAt(targetCamera.Translation(), m_pivotFn())));
|
||||
m_nextCamera.m_pitch = angles.GetX();
|
||||
m_nextCamera.m_yaw = angles.GetZ();
|
||||
m_nextCamera.m_pivot = targetCamera.m_pivot;
|
||||
}
|
||||
|
||||
// end the behavior when the camera is in alignment
|
||||
if (AZ::IsCloseMag(targetCamera.m_pitch, m_nextCamera.m_pitch) && AZ::IsCloseMag(targetCamera.m_yaw, m_nextCamera.m_yaw))
|
||||
{
|
||||
EndActivation();
|
||||
}
|
||||
|
||||
return m_nextCamera;
|
||||
}
|
||||
|
||||
void FocusCameraInput::SetPivotFn(PivotFn pivotFn)
|
||||
{
|
||||
m_pivotFn = AZStd::move(pivotFn);
|
||||
}
|
||||
|
||||
void FocusCameraInput::SetFocusInputChannelId(const InputChannelId& focusChannelId)
|
||||
{
|
||||
m_focusChannelId = focusChannelId;
|
||||
}
|
||||
|
||||
bool CustomCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta)
|
||||
{
|
||||
return m_handleEventsFn(*this, event, cursorDelta, scrollDelta);
|
||||
}
|
||||
|
||||
Camera CustomCameraInput::StepCamera(
|
||||
const Camera& targetCamera, const ScreenVector& cursorDelta, const float scrollDelta, const float deltaTime)
|
||||
{
|
||||
return m_stepCameraFn(*this, targetCamera, cursorDelta, scrollDelta, deltaTime);
|
||||
}
|
||||
|
||||
InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize)
|
||||
{
|
||||
const auto& inputChannelId = inputChannel.GetInputChannelId();
|
||||
|
||||
@@ -30,8 +30,11 @@ namespace AzFramework
|
||||
AZ::Vector3 EulerAngles(const AZ::Matrix3x3& orientation);
|
||||
|
||||
//! A simple camera representation using spherical coordinates as input (pitch, yaw, pivot and offset).
|
||||
//! The cameras transform and view can be obtained through accessor functions that use the internal
|
||||
//! The camera's transform and view can be obtained through accessor functions that use the internal
|
||||
//! spherical coordinates to calculate the position and orientation.
|
||||
//! @note Modifying m_pivot directly and leaving m_offset as zero will produce a free look camera effect, giving
|
||||
//! m_offset a value (e.g. in negative Y only) will produce an orbit camera effect, modifying X and Z of m_offset
|
||||
//! will further alter the camera translation in relation to m_pivot so it appears off center.
|
||||
struct Camera
|
||||
{
|
||||
AZ::Vector3 m_pivot = AZ::Vector3::CreateZero(); //!< Pivot point to rotate about (modified in world space).
|
||||
@@ -291,7 +294,7 @@ namespace AzFramework
|
||||
Cameras m_cameras; //!< Represents a collection of camera inputs that together provide a camera controller.
|
||||
|
||||
private:
|
||||
ScreenVector m_motionDelta; //!< The delta used for look/pivot/pan (rotation + translation) - two dimensional.
|
||||
ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional.
|
||||
CursorState m_cursorState; //!< The current and previous position of the cursor (used to calculate movement delta).
|
||||
float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional.
|
||||
bool m_handlingEvents = false; //!< Is the camera system currently handling events (events are consumed and not propagated).
|
||||
@@ -316,7 +319,7 @@ namespace AzFramework
|
||||
return AZStd::fmod(yaw + AZ::Constants::TwoPi, AZ::Constants::TwoPi);
|
||||
}
|
||||
|
||||
//! A camera input to handle motion deltas that can rotate or pivot the camera.
|
||||
//! A camera input to handle motion deltas that can change the orientation of the camera (update pitch and yaw).
|
||||
class RotateCameraInput : public CameraInput
|
||||
{
|
||||
public:
|
||||
@@ -348,15 +351,16 @@ namespace AzFramework
|
||||
//! PanAxes build function that will return a pair of pan axes depending on the camera orientation.
|
||||
using PanAxesFn = AZStd::function<PanAxes(const Camera& camera)>;
|
||||
|
||||
//! PanAxes to use while in 'look' camera behavior (free look).
|
||||
//! PanAxes to use while in 'look' or 'orbit' camera behavior.
|
||||
inline PanAxes LookPan(const Camera& camera)
|
||||
{
|
||||
const AZ::Matrix3x3 orientation = camera.Rotation();
|
||||
return { orientation.GetBasisX(), orientation.GetBasisZ() };
|
||||
}
|
||||
|
||||
//! PanAxes to use while in 'pivot' camera behavior.
|
||||
inline PanAxes PivotPan(const Camera& camera)
|
||||
//! Optional PanAxes to use while in 'orbit' camera behavior.
|
||||
//! @note This will move the camera in the local X/Y plane instead of usual X/Z plane.
|
||||
inline PanAxes OrbitPan(const Camera& camera)
|
||||
{
|
||||
const AZ::Matrix3x3 orientation = camera.Rotation();
|
||||
|
||||
@@ -370,14 +374,23 @@ namespace AzFramework
|
||||
return { basisX, basisY };
|
||||
}
|
||||
|
||||
//! TranslationDeltaFn is used by PanCameraInput and TranslateCameraInput
|
||||
//! @note Choose the appropriate function if the behavior should be operating as a free look camera (TranslatePivotLook)
|
||||
//! or an orbit camera (TranslateOffsetOrbit).
|
||||
using TranslationDeltaFn = AZStd::function<void(Camera& camera, const AZ::Vector3& delta)>;
|
||||
|
||||
inline void TranslatePivot(Camera& camera, const AZ::Vector3& delta)
|
||||
//! Update the pivot camera position.
|
||||
//! @note delta will need to have been transformed to world space, e.g. To move the camera right, (1, 0, 0) must
|
||||
//! first be transformed by the orientation of the camera before being applied to m_pivot.
|
||||
inline void TranslatePivotLook(Camera& camera, const AZ::Vector3& delta)
|
||||
{
|
||||
camera.m_pivot += delta;
|
||||
}
|
||||
|
||||
inline void TranslateOffset(Camera& camera, const AZ::Vector3& delta)
|
||||
//! Update the offset camera position.
|
||||
//! @note delta still needs to be transformed to world space (as with TranslatePivotLook) but internally this is undone
|
||||
//! to be performed in local space when being applied to m_offset.
|
||||
inline void TranslateOffsetOrbit(Camera& camera, const AZ::Vector3& delta)
|
||||
{
|
||||
camera.m_offset += camera.View().TransformVector(delta);
|
||||
}
|
||||
@@ -409,7 +422,7 @@ namespace AzFramework
|
||||
//! Axes to use while translating the camera.
|
||||
using TranslationAxesFn = AZStd::function<AZ::Matrix3x3(const Camera& camera)>;
|
||||
|
||||
//! TranslationAxes to use while in 'look' camera behavior (free look).
|
||||
//! TranslationAxes to use while in 'look' or 'orbit' camera behavior.
|
||||
inline AZ::Matrix3x3 LookTranslation(const Camera& camera)
|
||||
{
|
||||
const AZ::Matrix3x3 orientation = camera.Rotation();
|
||||
@@ -421,8 +434,8 @@ namespace AzFramework
|
||||
return AZ::Matrix3x3::CreateFromColumns(basisX, basisY, basisZ);
|
||||
}
|
||||
|
||||
//! TranslationAxes to use while in 'pivot' camera behavior.
|
||||
inline AZ::Matrix3x3 PivotTranslation(const Camera& camera)
|
||||
//! Optional TranslationAxes to use while in 'orbit' camera behavior.
|
||||
inline AZ::Matrix3x3 OrbitTranslation(const Camera& camera)
|
||||
{
|
||||
const AZ::Matrix3x3 orientation = camera.Rotation();
|
||||
|
||||
@@ -535,11 +548,11 @@ namespace AzFramework
|
||||
bool m_boost = false; //!< Is the translation speed currently being multiplied/scaled upwards.
|
||||
};
|
||||
|
||||
//! A camera input to handle discrete scroll events that can modify the camera pivot distance.
|
||||
class PivotDollyScrollCameraInput : public CameraInput
|
||||
//! A camera input to handle discrete scroll events that can modify the camera offset.
|
||||
class OrbitDollyScrollCameraInput : public CameraInput
|
||||
{
|
||||
public:
|
||||
PivotDollyScrollCameraInput();
|
||||
OrbitDollyScrollCameraInput();
|
||||
|
||||
// CameraInput overrides ...
|
||||
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
|
||||
@@ -548,11 +561,11 @@ namespace AzFramework
|
||||
AZStd::function<float()> m_scrollSpeedFn;
|
||||
};
|
||||
|
||||
//! A camera input to handle motion deltas that can modify the camera pivot distance.
|
||||
class PivotDollyMotionCameraInput : public CameraInput
|
||||
//! A camera input to handle motion deltas that can modify the camera offset.
|
||||
class OrbitDollyMotionCameraInput : public CameraInput
|
||||
{
|
||||
public:
|
||||
explicit PivotDollyMotionCameraInput(const InputChannelId& dollyChannelId);
|
||||
explicit OrbitDollyMotionCameraInput(const InputChannelId& dollyChannelId);
|
||||
|
||||
// CameraInput overrides ...
|
||||
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
|
||||
@@ -569,10 +582,10 @@ namespace AzFramework
|
||||
};
|
||||
|
||||
//! A camera input to handle discrete scroll events that can scroll (translate) the camera along its forward axis.
|
||||
class ScrollTranslationCameraInput : public CameraInput
|
||||
class LookScrollTranslationCameraInput : public CameraInput
|
||||
{
|
||||
public:
|
||||
ScrollTranslationCameraInput();
|
||||
LookScrollTranslationCameraInput();
|
||||
|
||||
// CameraInput overrides ...
|
||||
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
|
||||
@@ -583,40 +596,96 @@ namespace AzFramework
|
||||
|
||||
//! A camera input that doubles as its own set of camera inputs.
|
||||
//! It is 'exclusive', so does not overlap with other sibling camera inputs - it runs its own set of camera inputs as 'children'.
|
||||
class PivotCameraInput : public CameraInput
|
||||
class OrbitCameraInput : public CameraInput
|
||||
{
|
||||
public:
|
||||
using PivotFn = AZStd::function<AZ::Vector3(const AZ::Vector3& position, const AZ::Vector3& direction)>;
|
||||
|
||||
explicit PivotCameraInput(const InputChannelId& pivotChannelId);
|
||||
explicit OrbitCameraInput(const InputChannelId& orbitChannelId);
|
||||
|
||||
// CameraInput overrides ...
|
||||
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;
|
||||
|
||||
void SetPivotInputChannelId(const InputChannelId& pivotChanneId);
|
||||
void SetOrbitInputChannelId(const InputChannelId& orbitChanneId);
|
||||
|
||||
Cameras m_pivotCameras; //!< The camera inputs to run when this camera input is active (only these will run as it is exclusive).
|
||||
Cameras m_orbitCameras; //!< The camera inputs to run when this camera input is active (only these will run as it is exclusive).
|
||||
|
||||
//! Override the default behavior for how a pivot point is calculated.
|
||||
void SetPivotFn(PivotFn pivotFn);
|
||||
|
||||
private:
|
||||
InputChannelId m_pivotChannelId; //!< Input channel to begin the pivot camera input.
|
||||
PivotFn m_pivotFn; //!< The pivot position to use for this pivot camera (how is the pivot point calculated/retrieved).
|
||||
InputChannelId m_orbitChannelId; //!< Input channel to begin the orbit camera input.
|
||||
PivotFn m_pivotFn; //!< The pivot position to use for this orbit camera (how is the pivot point calculated/retrieved).
|
||||
};
|
||||
|
||||
inline void PivotCameraInput::SetPivotFn(PivotFn pivotFn)
|
||||
inline void OrbitCameraInput::SetPivotFn(PivotFn pivotFn)
|
||||
{
|
||||
m_pivotFn = AZStd::move(pivotFn);
|
||||
}
|
||||
|
||||
inline bool PivotCameraInput::Exclusive() const
|
||||
inline bool OrbitCameraInput::Exclusive() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Callback to use for FocusCameraInput when a free look camera is being used.
|
||||
//! @note This is when offset is zero.
|
||||
inline AZ::Vector3 FocusLook(float)
|
||||
{
|
||||
return AZ::Vector3::CreateZero();
|
||||
}
|
||||
|
||||
//! Callback to use for FocusCameraInput when a orbit camera is being used.
|
||||
//! @note This is when offset is non zero.
|
||||
inline AZ::Vector3 FocusOrbit(const float length)
|
||||
{
|
||||
return AZ::Vector3::CreateAxisY(-length);
|
||||
}
|
||||
|
||||
using FocusOffsetFn = AZStd::function<AZ::Vector3(float)>;
|
||||
|
||||
//! A focus behavior to align the camera view to the position returned by the pivot function.
|
||||
//! @note This only alters the camera orientation, the translation is unaffected.
|
||||
class FocusCameraInput : public CameraInput
|
||||
{
|
||||
public:
|
||||
using PivotFn = AZStd::function<AZ::Vector3()>;
|
||||
|
||||
FocusCameraInput(const InputChannelId& focusChannelId, FocusOffsetFn offsetFn);
|
||||
|
||||
// CameraInput overrides ...
|
||||
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
|
||||
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
|
||||
|
||||
//! Override the default behavior for how a pivot point is calculated.
|
||||
void SetPivotFn(PivotFn pivotFn);
|
||||
|
||||
void SetFocusInputChannelId(const InputChannelId& focusChannelId);
|
||||
|
||||
private:
|
||||
InputChannelId m_focusChannelId; //!< Input channel to begin the focus camera input.
|
||||
Camera m_nextCamera;
|
||||
PivotFn m_pivotFn;
|
||||
FocusOffsetFn m_offsetFn;
|
||||
};
|
||||
|
||||
//! Provides a CameraInput type that can be implemented without needing to create a new type deriving from CameraInput.
|
||||
//! This can be very useful for specific use cases that are less generally applicable.
|
||||
class CustomCameraInput : public CameraInput
|
||||
{
|
||||
public:
|
||||
// CameraInput overrides ...
|
||||
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
|
||||
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
|
||||
|
||||
//! HandleEvents delegates directly to m_handleEventsFn.
|
||||
AZStd::function<bool(CameraInput&, const InputEvent&, const ScreenVector&, float)> m_handleEventsFn;
|
||||
//! StepCamera delegates directly to m_stepCameraFn.
|
||||
AZStd::function<Camera(CameraInput&, const Camera&, const ScreenVector&, float, float)> m_stepCameraFn;
|
||||
};
|
||||
|
||||
//! Map from a generic InputChannel event to a camera specific InputEvent.
|
||||
InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize);
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -167,6 +167,10 @@ set(FILES
|
||||
Logging/MissingAssetLogger.cpp
|
||||
Logging/MissingAssetLogger.h
|
||||
Logging/MissingAssetNotificationBus.h
|
||||
Matchmaking/IMatchmakingRequests.h
|
||||
Matchmaking/MatchmakingRequests.cpp
|
||||
Matchmaking/MatchmakingRequests.h
|
||||
Matchmaking/MatchmakingNotifications.h
|
||||
Scene/Scene.h
|
||||
Scene/Scene.inl
|
||||
Scene/Scene.cpp
|
||||
@@ -181,8 +185,9 @@ set(FILES
|
||||
Script/ScriptRemoteDebugging.cpp
|
||||
Script/ScriptRemoteDebugging.h
|
||||
Session/ISessionHandlingRequests.h
|
||||
Session/ISessionRequests.cpp
|
||||
Session/ISessionRequests.h
|
||||
Session/SessionRequests.cpp
|
||||
Session/SessionRequests.h
|
||||
Session/SessionConfig.cpp
|
||||
Session/SessionConfig.h
|
||||
Session/SessionNotifications.h
|
||||
|
||||
+116
-20
@@ -20,6 +20,15 @@
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
// xcb-xkb does not provide a generic event type, so we define our own.
|
||||
// These fields are enough to get to the xkbType field, which can then be
|
||||
// read to typecast the event to the right concrete type.
|
||||
struct XcbXkbGenericEventT
|
||||
{
|
||||
uint8_t response_type;
|
||||
uint8_t xkbType;
|
||||
};
|
||||
|
||||
XcbInputDeviceKeyboard::XcbInputDeviceKeyboard(InputDeviceKeyboard& inputDevice)
|
||||
: InputDeviceKeyboard::Implementation(inputDevice)
|
||||
{
|
||||
@@ -39,19 +48,22 @@ namespace AzFramework
|
||||
return;
|
||||
}
|
||||
|
||||
XcbStdFreePtr<xcb_xkb_use_extension_reply_t> xkbUseExtensionReply{
|
||||
xcb_xkb_use_extension_reply(connection, xcb_xkb_use_extension(connection, 1, 0), nullptr)
|
||||
};
|
||||
if (!xkbUseExtensionReply)
|
||||
int initializeXkbExtensionSuccess = xkb_x11_setup_xkb_extension(
|
||||
connection,
|
||||
1,
|
||||
0,
|
||||
XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&m_xkbEventCode,
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (!initializeXkbExtensionSuccess)
|
||||
{
|
||||
AZ_Warning("ApplicationLinux", false, "Failed to initialize the xkb extension");
|
||||
return;
|
||||
}
|
||||
if (!xkbUseExtensionReply->supported)
|
||||
{
|
||||
AZ_Warning("ApplicationLinux", false, "The X server does not support the xkb extension");
|
||||
return;
|
||||
}
|
||||
|
||||
m_coreDeviceId = xkb_x11_get_core_keyboard_device_id(connection);
|
||||
|
||||
@@ -59,6 +71,43 @@ namespace AzFramework
|
||||
m_xkbKeymap.reset(xkb_x11_keymap_new_from_device(m_xkbContext.get(), connection, m_coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS));
|
||||
m_xkbState.reset(xkb_x11_state_new_from_device(m_xkbKeymap.get(), connection, m_coreDeviceId));
|
||||
|
||||
const uint16_t affectMap =
|
||||
XCB_XKB_MAP_PART_KEY_TYPES
|
||||
| XCB_XKB_MAP_PART_KEY_SYMS
|
||||
| XCB_XKB_MAP_PART_MODIFIER_MAP
|
||||
| XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS
|
||||
| XCB_XKB_MAP_PART_KEY_ACTIONS
|
||||
| XCB_XKB_MAP_PART_KEY_BEHAVIORS
|
||||
| XCB_XKB_MAP_PART_VIRTUAL_MODS
|
||||
| XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP
|
||||
;
|
||||
|
||||
const uint16_t selectedEvents =
|
||||
XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY
|
||||
| XCB_XKB_EVENT_TYPE_MAP_NOTIFY
|
||||
| XCB_XKB_EVENT_TYPE_STATE_NOTIFY
|
||||
;
|
||||
|
||||
XcbStdFreePtr<xcb_generic_error_t> error{xcb_request_check(
|
||||
connection,
|
||||
xcb_xkb_select_events(
|
||||
connection,
|
||||
/* deviceSpec = */ XCB_XKB_ID_USE_CORE_KBD,
|
||||
/* affectWhich = */ selectedEvents,
|
||||
/* clear = */ 0,
|
||||
/* selectAll = */ selectedEvents,
|
||||
/* affectMap = */ affectMap,
|
||||
/* map = */ affectMap,
|
||||
/* details = */ nullptr
|
||||
)
|
||||
)};
|
||||
|
||||
if (error)
|
||||
{
|
||||
AZ_Warning("ApplicationLinux", false, "failed to select notify events from XKB");
|
||||
return;
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
@@ -70,15 +119,17 @@ namespace AzFramework
|
||||
|
||||
bool XcbInputDeviceKeyboard::HasTextEntryStarted() const
|
||||
{
|
||||
return false;
|
||||
return m_hasTextEntryStarted;
|
||||
}
|
||||
|
||||
void XcbInputDeviceKeyboard::TextEntryStart(const InputDeviceKeyboard::VirtualKeyboardOptions& options)
|
||||
{
|
||||
m_hasTextEntryStarted = true;
|
||||
}
|
||||
|
||||
void XcbInputDeviceKeyboard::TextEntryStop()
|
||||
{
|
||||
m_hasTextEntryStarted = false;
|
||||
}
|
||||
|
||||
void XcbInputDeviceKeyboard::TickInputDevice()
|
||||
@@ -93,30 +144,45 @@ namespace AzFramework
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event->response_type & ~0x80)
|
||||
const auto responseType = event->response_type & ~0x80;
|
||||
if (responseType == XCB_KEY_PRESS)
|
||||
{
|
||||
case XCB_KEY_PRESS:
|
||||
{
|
||||
auto* keyPress = reinterpret_cast<xcb_key_press_event_t*>(event);
|
||||
const auto* keyPress = reinterpret_cast<xcb_key_press_event_t*>(event);
|
||||
{
|
||||
auto text = TextFromKeycode(m_xkbState.get(), keyPress->detail);
|
||||
if (!text.empty())
|
||||
{
|
||||
QueueRawTextEvent(AZStd::move(text));
|
||||
}
|
||||
}
|
||||
|
||||
const InputChannelId* key = InputChannelFromKeyEvent(keyPress->detail);
|
||||
if (key)
|
||||
if (const InputChannelId* key = InputChannelFromKeyEvent(keyPress->detail))
|
||||
{
|
||||
QueueRawKeyEvent(*key, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case XCB_KEY_RELEASE:
|
||||
else if (responseType == XCB_KEY_RELEASE)
|
||||
{
|
||||
auto* keyRelease = reinterpret_cast<xcb_key_release_event_t*>(event);
|
||||
const auto* keyRelease = reinterpret_cast<xcb_key_release_event_t*>(event);
|
||||
|
||||
const InputChannelId* key = InputChannelFromKeyEvent(keyRelease->detail);
|
||||
if (key)
|
||||
{
|
||||
QueueRawKeyEvent(*key, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (responseType == m_xkbEventCode)
|
||||
{
|
||||
const auto* xkbEvent = reinterpret_cast<XcbXkbGenericEventT*>(event);
|
||||
switch (xkbEvent->xkbType)
|
||||
{
|
||||
case XCB_XKB_STATE_NOTIFY:
|
||||
{
|
||||
const auto* stateNotifyEvent = reinterpret_cast<xcb_xkb_state_notify_event_t*>(event);
|
||||
UpdateState(stateNotifyEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,4 +334,34 @@ namespace AzFramework
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AZStd::string XcbInputDeviceKeyboard::TextFromKeycode(xkb_state* state, xkb_keycode_t code)
|
||||
{
|
||||
// Find out how much of a buffer we need
|
||||
const size_t size = xkb_state_key_get_utf8(state, code, nullptr, 0);
|
||||
if (!size)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
// xkb_state_key_get_utf8 will null-terminate the resulting string, and
|
||||
// will truncate the result to `size - 1` if there is not enough space
|
||||
// for the null byte. The first call returns the size of the resulting
|
||||
// string without including the null byte. AZStd::string internally
|
||||
// includes space for the null byte, but that is not included in its
|
||||
// `size()`. xkb_state_key_get_utf8 will always set `buf[size - 1] =
|
||||
// 0`, so add 1 to `chars.size()` to include that internal null byte in
|
||||
// the string.
|
||||
AZStd::string chars;
|
||||
chars.resize_no_construct(size);
|
||||
xkb_state_key_get_utf8(state, code, chars.data(), chars.size() + 1);
|
||||
return chars;
|
||||
}
|
||||
|
||||
void XcbInputDeviceKeyboard::UpdateState(const xcb_xkb_state_notify_event_t* state)
|
||||
{
|
||||
if (m_initialized)
|
||||
{
|
||||
xkb_state_update_mask(m_xkbState.get(), state->baseMods, state->latchedMods, state->lockedMods, state->baseGroup, state->latchedGroup, state->lockedGroup);
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <xcb/xcb.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
struct xcb_xkb_state_notify_event_t;
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class XcbInputDeviceKeyboard
|
||||
@@ -37,10 +39,16 @@ namespace AzFramework
|
||||
private:
|
||||
[[nodiscard]] const InputChannelId* InputChannelFromKeyEvent(xcb_keycode_t code) const;
|
||||
|
||||
static AZStd::string TextFromKeycode(xkb_state* state, xkb_keycode_t code);
|
||||
|
||||
void UpdateState(const xcb_xkb_state_notify_event_t* state);
|
||||
|
||||
XcbUniquePtr<xkb_context, xkb_context_unref> m_xkbContext;
|
||||
XcbUniquePtr<xkb_keymap, xkb_keymap_unref> m_xkbKeymap;
|
||||
XcbUniquePtr<xkb_state, xkb_state_unref> m_xkbState;
|
||||
int m_coreDeviceId{-1};
|
||||
uint8_t m_xkbEventCode{0};
|
||||
bool m_initialized{false};
|
||||
bool m_hasTextEntryStarted{false};
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -53,31 +53,31 @@ namespace UnitTest
|
||||
};
|
||||
|
||||
m_firstPersonTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(
|
||||
m_translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivot);
|
||||
m_translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivotLook);
|
||||
|
||||
m_pivotCamera = AZStd::make_shared<AzFramework::PivotCameraInput>(m_pivotChannelId);
|
||||
m_pivotCamera->SetPivotFn(
|
||||
m_orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>(m_orbitChannelId);
|
||||
m_orbitCamera->SetPivotFn(
|
||||
[this](const AZ::Vector3&, const AZ::Vector3&)
|
||||
{
|
||||
return m_pivot;
|
||||
});
|
||||
|
||||
auto pivotRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Left);
|
||||
auto orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Left);
|
||||
// set rotate speed to be a value that will scale motion delta (pixels moved) by a thousandth.
|
||||
pivotRotateCamera->m_rotateSpeedFn = []()
|
||||
orbitRotateCamera->m_rotateSpeedFn = []()
|
||||
{
|
||||
return 0.001f;
|
||||
};
|
||||
|
||||
auto pivotTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(
|
||||
m_translateCameraInputChannelIds, AzFramework::PivotTranslation, AzFramework::TranslateOffset);
|
||||
auto orbitTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(
|
||||
m_translateCameraInputChannelIds, AzFramework::OrbitTranslation, AzFramework::TranslateOffsetOrbit);
|
||||
|
||||
m_pivotCamera->m_pivotCameras.AddCamera(pivotRotateCamera);
|
||||
m_pivotCamera->m_pivotCameras.AddCamera(pivotTranslateCamera);
|
||||
m_orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera);
|
||||
m_orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera);
|
||||
|
||||
m_cameraSystem->m_cameras.AddCamera(m_firstPersonRotateCamera);
|
||||
m_cameraSystem->m_cameras.AddCamera(m_firstPersonTranslateCamera);
|
||||
m_cameraSystem->m_cameras.AddCamera(m_pivotCamera);
|
||||
m_cameraSystem->m_cameras.AddCamera(m_orbitCamera);
|
||||
|
||||
// these tests rely on using motion delta, not cursor positions (default is true)
|
||||
AzFramework::ed_cameraSystemUseCursor = false;
|
||||
@@ -87,7 +87,7 @@ namespace UnitTest
|
||||
{
|
||||
AzFramework::ed_cameraSystemUseCursor = true;
|
||||
|
||||
m_pivotCamera.reset();
|
||||
m_orbitCamera.reset();
|
||||
m_firstPersonRotateCamera.reset();
|
||||
m_firstPersonTranslateCamera.reset();
|
||||
|
||||
@@ -97,11 +97,11 @@ namespace UnitTest
|
||||
AllocatorsTestFixture::TearDown();
|
||||
}
|
||||
|
||||
AzFramework::InputChannelId m_pivotChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l");
|
||||
AzFramework::InputChannelId m_orbitChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l");
|
||||
AzFramework::TranslateCameraInputChannelIds m_translateCameraInputChannelIds;
|
||||
AZStd::shared_ptr<AzFramework::RotateCameraInput> m_firstPersonRotateCamera;
|
||||
AZStd::shared_ptr<AzFramework::TranslateCameraInput> m_firstPersonTranslateCamera;
|
||||
AZStd::shared_ptr<AzFramework::PivotCameraInput> m_pivotCamera;
|
||||
AZStd::shared_ptr<AzFramework::OrbitCameraInput> m_orbitCamera;
|
||||
AZ::Vector3 m_pivot = AZ::Vector3::CreateZero();
|
||||
|
||||
//! This is approximately Pi/2 * 1000 - this can be used to rotate the camera 90 degrees (pitch or yaw based
|
||||
@@ -109,17 +109,17 @@ namespace UnitTest
|
||||
inline static const int PixelMotionDelta = 1570;
|
||||
};
|
||||
|
||||
TEST_F(CameraInputFixture, BeginAndEndPivotCameraInputConsumesCorrectEvents)
|
||||
TEST_F(CameraInputFixture, BeginAndEndOrbitCameraInputConsumesCorrectEvents)
|
||||
{
|
||||
// begin pivot camera
|
||||
// begin orbit camera
|
||||
const bool consumed1 = HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceKeyboard::Key::ModifierAltL,
|
||||
AzFramework::InputChannel::State::Began });
|
||||
// begin listening for pivot rotate (click detector) - event is not consumed
|
||||
// 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 pivot rotate (mouse has moved sufficient distance to initiate)
|
||||
// begin orbit rotate (mouse has moved sufficient distance to initiate)
|
||||
const bool consumed3 = HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ 5 });
|
||||
// end pivot (mouse up) - event is not consumed
|
||||
// end orbit (mouse up) - event is not consumed
|
||||
const bool consumed4 = HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended });
|
||||
|
||||
@@ -260,10 +260,10 @@ namespace UnitTest
|
||||
EXPECT_TRUE(activationEnded);
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, PivotCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenPivoting)
|
||||
TEST_F(CameraInputFixture, OrbitCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenOrbiting)
|
||||
{
|
||||
// create pathological lookAtFn that just returns the same position as the camera
|
||||
m_pivotCamera->SetPivotFn(
|
||||
m_orbitCamera->SetPivotFn(
|
||||
[](const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction)
|
||||
{
|
||||
return position;
|
||||
@@ -275,7 +275,7 @@ namespace UnitTest
|
||||
AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), expectedCameraPosition));
|
||||
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
|
||||
|
||||
// verify the camera yaw has not changed and pivot point matches the expected camera position
|
||||
using ::testing::FloatNear;
|
||||
@@ -321,14 +321,14 @@ namespace UnitTest
|
||||
EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero()));
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, PivotRotateCameraInputRotatesPitchOffsetByNinetyDegreesWithRequiredPixelDelta)
|
||||
TEST_F(CameraInputFixture, OrbitRotateCameraInputRotatesPitchOffsetByNinetyDegreesWithRequiredPixelDelta)
|
||||
{
|
||||
const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-20.0f);
|
||||
m_targetCamera.m_pivot = cameraStartingPosition;
|
||||
|
||||
m_pivot = AZ::Vector3::CreateAxisY(-10.0f);
|
||||
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta });
|
||||
@@ -344,14 +344,14 @@ namespace UnitTest
|
||||
EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(expectedCameraEndingPosition, 0.01f));
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, PivotRotateCameraInputRotatesYawOffsetByNinetyDegreesWithRequiredPixelDelta)
|
||||
TEST_F(CameraInputFixture, OrbitRotateCameraInputRotatesYawOffsetByNinetyDegreesWithRequiredPixelDelta)
|
||||
{
|
||||
const auto cameraStartingPosition = AZ::Vector3(15.0f, -20.0f, 0.0f);
|
||||
m_targetCamera.m_pivot = cameraStartingPosition;
|
||||
|
||||
m_pivot = AZ::Vector3(10.0f, -10.0f, 0.0f);
|
||||
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ -PixelMotionDelta });
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
inline testing::PolymorphicMatcher<testing::internal::StrEqualityMatcher<AZStd::string>> StrEq(const AZStd::string& str)
|
||||
{
|
||||
return ::testing::MakePolymorphicMatcher(testing::internal::StrEqualityMatcher<AZStd::string>(str, true, true));
|
||||
}
|
||||
@@ -28,6 +28,10 @@ xcb_generic_event_t* xcb_poll_for_event(xcb_connection_t* c)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_poll_for_event(c);
|
||||
}
|
||||
xcb_generic_error_t* xcb_request_check(xcb_connection_t* c, xcb_void_cookie_t cookie)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_request_check(c, cookie);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// xcb-xkb
|
||||
@@ -39,6 +43,10 @@ xcb_xkb_use_extension_reply_t* xcb_xkb_use_extension_reply(xcb_connection_t* c,
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xkb_use_extension_reply(c, cookie, e);
|
||||
}
|
||||
xcb_void_cookie_t xcb_xkb_select_events(xcb_connection_t* c, xcb_xkb_device_spec_t deviceSpec, uint16_t affectWhich, uint16_t clear, uint16_t selectAll, uint16_t affectMap, uint16_t map, const void* details)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xkb_select_events(c, deviceSpec, affectWhich, clear, selectAll, affectMap, map, details);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// xkb-x11
|
||||
@@ -46,7 +54,7 @@ int32_t xkb_x11_get_core_keyboard_device_id(xcb_connection_t* connection)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xkb_x11_get_core_keyboard_device_id(connection);
|
||||
}
|
||||
struct xkb_keymap* xkb_x11_keymap_new_from_device(struct xkb_context* context, xcb_connection_t* connection, int32_t device_id, enum xkb_keymap_compile_flags flags)
|
||||
xkb_keymap* xkb_x11_keymap_new_from_device(xkb_context* context, xcb_connection_t* connection, int32_t device_id, xkb_keymap_compile_flags flags)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xkb_x11_keymap_new_from_device(context, connection, device_id, flags);
|
||||
}
|
||||
@@ -54,28 +62,58 @@ xkb_state* xkb_x11_state_new_from_device(xkb_keymap* keymap, xcb_connection_t* c
|
||||
{
|
||||
return MockXcbInterface::Instance()->xkb_x11_state_new_from_device(keymap, connection, device_id);
|
||||
}
|
||||
int xkb_x11_setup_xkb_extension(
|
||||
xcb_connection_t* connection,
|
||||
uint16_t major_xkb_version,
|
||||
uint16_t minor_xkb_version,
|
||||
xkb_x11_setup_xkb_extension_flags flags,
|
||||
uint16_t* major_xkb_version_out,
|
||||
uint16_t* minor_xkb_version_out,
|
||||
uint8_t* base_event_out,
|
||||
uint8_t* base_error_out)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xkb_x11_setup_xkb_extension(
|
||||
connection, major_xkb_version, minor_xkb_version, flags, major_xkb_version_out, minor_xkb_version_out, base_event_out,
|
||||
base_error_out);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// xkbcommon
|
||||
xkb_context* xkb_context_new(enum xkb_context_flags flags)
|
||||
xkb_context* xkb_context_new(xkb_context_flags flags)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xkb_context_new(flags);
|
||||
}
|
||||
void xkb_context_unref(xkb_context *context)
|
||||
void xkb_context_unref(xkb_context* context)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xkb_context_unref(context);
|
||||
}
|
||||
void xkb_keymap_unref(xkb_keymap *keymap)
|
||||
void xkb_keymap_unref(xkb_keymap* keymap)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xkb_keymap_unref(keymap);
|
||||
}
|
||||
void xkb_state_unref(xkb_state *state)
|
||||
void xkb_state_unref(xkb_state* state)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xkb_state_unref(state);
|
||||
}
|
||||
xkb_keysym_t xkb_state_key_get_one_sym(xkb_state *state, xkb_keycode_t key)
|
||||
xkb_keysym_t xkb_state_key_get_one_sym(xkb_state* state, xkb_keycode_t key)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xkb_state_key_get_one_sym(state, key);
|
||||
}
|
||||
int xkb_state_key_get_utf8(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xkb_state_key_get_utf8(state, key, buffer, size);
|
||||
}
|
||||
xkb_state_component xkb_state_update_mask(
|
||||
xkb_state* state,
|
||||
xkb_mod_mask_t depressed_mods,
|
||||
xkb_mod_mask_t latched_mods,
|
||||
xkb_mod_mask_t locked_mods,
|
||||
xkb_layout_index_t depressed_layout,
|
||||
xkb_layout_index_t latched_layout,
|
||||
xkb_layout_index_t locked_layout)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xkb_state_update_mask(
|
||||
state, depressed_mods, latched_mods, locked_mods, depressed_layout, latched_layout, locked_layout);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <xcb/xkb.h>
|
||||
#undef explicit
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-x11.h>
|
||||
|
||||
#include "Printers.h"
|
||||
|
||||
@@ -35,6 +36,7 @@ struct xkb_keymap
|
||||
|
||||
struct xkb_state
|
||||
{
|
||||
xkb_mod_mask_t m_modifiers{};
|
||||
};
|
||||
|
||||
class MockXcbInterface
|
||||
@@ -48,7 +50,7 @@ public:
|
||||
MockXcbInterface(MockXcbInterface&&) = delete;
|
||||
MockXcbInterface& operator=(const MockXcbInterface&) = delete;
|
||||
MockXcbInterface& operator=(MockXcbInterface&&) = delete;
|
||||
~MockXcbInterface()
|
||||
virtual ~MockXcbInterface()
|
||||
{
|
||||
self = nullptr;
|
||||
}
|
||||
@@ -59,22 +61,27 @@ public:
|
||||
MOCK_CONST_METHOD2(xcb_connect, xcb_connection_t*(const char* displayname, int* screenp));
|
||||
MOCK_CONST_METHOD1(xcb_disconnect, void(xcb_connection_t* c));
|
||||
MOCK_CONST_METHOD1(xcb_poll_for_event, xcb_generic_event_t*(xcb_connection_t* c));
|
||||
MOCK_CONST_METHOD2(xcb_request_check, xcb_generic_error_t*(xcb_connection_t* c, xcb_void_cookie_t cookie));
|
||||
|
||||
// xcb-xkb
|
||||
MOCK_CONST_METHOD3(xcb_xkb_use_extension, xcb_xkb_use_extension_cookie_t(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor));
|
||||
MOCK_CONST_METHOD3(xcb_xkb_use_extension_reply, xcb_xkb_use_extension_reply_t*(xcb_connection_t* c, xcb_xkb_use_extension_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD8(xcb_xkb_select_events, xcb_void_cookie_t(xcb_connection_t* c, xcb_xkb_device_spec_t deviceSpec, uint16_t affectWhich, uint16_t clear, uint16_t selectAll, uint16_t affectMap, uint16_t map, const void* details));
|
||||
|
||||
// xkb-x11
|
||||
MOCK_CONST_METHOD1(xkb_x11_get_core_keyboard_device_id, int32_t(xcb_connection_t* connection));
|
||||
MOCK_CONST_METHOD4(xkb_x11_keymap_new_from_device, xkb_keymap*(xkb_context* context, xcb_connection_t* connection, int32_t device_id, xkb_keymap_compile_flags flags));
|
||||
MOCK_CONST_METHOD3(xkb_x11_state_new_from_device, xkb_state*(xkb_keymap* keymap, xcb_connection_t* connection, int32_t device_id));
|
||||
MOCK_CONST_METHOD8(xkb_x11_setup_xkb_extension, int(xcb_connection_t* connection, uint16_t major_xkb_version, uint16_t minor_xkb_version, xkb_x11_setup_xkb_extension_flags flags, uint16_t* major_xkb_version_out, uint16_t* minor_xkb_version_out, uint8_t* base_event_out, uint8_t* base_error_out));
|
||||
|
||||
// xkbcommon
|
||||
MOCK_CONST_METHOD1(xkb_context_new, xkb_context*(xkb_context_flags flags));
|
||||
MOCK_CONST_METHOD1(xkb_context_unref, void(xkb_context* context));
|
||||
MOCK_CONST_METHOD1(xkb_keymap_unref, void(xkb_keymap* keymap));
|
||||
MOCK_CONST_METHOD1(xkb_state_unref, void(xkb_state* state));
|
||||
MOCK_CONST_METHOD2(xkb_state_key_get_one_sym, xkb_keysym_t(xkb_state *state, xkb_keycode_t key));
|
||||
MOCK_CONST_METHOD2(xkb_state_key_get_one_sym, xkb_keysym_t(xkb_state* state, xkb_keycode_t key));
|
||||
MOCK_CONST_METHOD4(xkb_state_key_get_utf8, int(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size));
|
||||
MOCK_CONST_METHOD7(xkb_state_update_mask, xkb_state_component(xkb_state* state, xkb_mod_mask_t depressed_mods, xkb_mod_mask_t latched_mods, xkb_mod_mask_t locked_mods, xkb_layout_index_t depressed_layout, xkb_layout_index_t latched_layout, xkb_layout_index_t locked_layout));
|
||||
|
||||
private:
|
||||
static inline MockXcbInterface* self = nullptr;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "XcbBaseTestFixture.h"
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
void XcbBaseTestFixture::SetUp()
|
||||
{
|
||||
using testing::Return;
|
||||
using testing::_;
|
||||
|
||||
testing::Test::SetUp();
|
||||
|
||||
EXPECT_CALL(m_interface, xcb_connect(_, _))
|
||||
.WillOnce(Return(&m_connection));
|
||||
EXPECT_CALL(m_interface, xcb_disconnect(&m_connection))
|
||||
.Times(1);
|
||||
}
|
||||
} // namespace AzFramework
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include "MockXcbInterface.h"
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
// Sets up mock behavior for the xcb library, providing an xcb_connection_t that is returned from a call to xcb_connect
|
||||
class XcbBaseTestFixture
|
||||
: public testing::Test
|
||||
{
|
||||
public:
|
||||
void SetUp() override;
|
||||
|
||||
protected:
|
||||
testing::NiceMock<MockXcbInterface> m_interface;
|
||||
xcb_connection_t m_connection{};
|
||||
};
|
||||
} // namespace AzFramework
|
||||
+335
-47
@@ -6,6 +6,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <gmock/gmock-actions.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
@@ -13,8 +14,11 @@
|
||||
|
||||
#include <AzFramework/XcbApplication.h>
|
||||
#include <AzFramework/XcbInputDeviceKeyboard.h>
|
||||
#include <AzFramework/Input/Buses/Notifications/InputTextNotificationBus.h>
|
||||
#include "MockXcbInterface.h"
|
||||
#include "Matchers.h"
|
||||
#include "Actions.h"
|
||||
#include "XcbBaseTestFixture.h"
|
||||
|
||||
template<typename T>
|
||||
xcb_generic_event_t MakeEvent(T event)
|
||||
@@ -24,26 +28,126 @@ xcb_generic_event_t MakeEvent(T event)
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
TEST(XcbInputDeviceKeyboard, InputChannelsUpdateStateFromXcbEvents)
|
||||
// Sets up default behavior for mock keyboard responses to xcb methods
|
||||
class XcbInputDeviceKeyboardTests
|
||||
: public XcbBaseTestFixture
|
||||
{
|
||||
using testing::Return;
|
||||
void SetUp() override
|
||||
{
|
||||
using testing::Return;
|
||||
using testing::SetArgPointee;
|
||||
using testing::_;
|
||||
|
||||
XcbBaseTestFixture::SetUp();
|
||||
EXPECT_CALL(m_interface, xkb_context_new(XKB_CONTEXT_NO_FLAGS))
|
||||
.WillOnce(Return(&m_xkbContext));
|
||||
EXPECT_CALL(m_interface, xkb_context_unref(&m_xkbContext))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_CALL(m_interface, xkb_x11_keymap_new_from_device(&m_xkbContext, &m_connection, s_coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS))
|
||||
.WillOnce(Return(&m_xkbKeymap));
|
||||
EXPECT_CALL(m_interface, xkb_keymap_unref(&m_xkbKeymap))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_CALL(m_interface, xkb_x11_state_new_from_device(&m_xkbKeymap, &m_connection, s_coreDeviceId))
|
||||
.WillOnce(Return(&m_xkbState));
|
||||
EXPECT_CALL(m_interface, xkb_state_unref(&m_xkbState))
|
||||
.Times(1);
|
||||
|
||||
ON_CALL(m_interface, xkb_x11_setup_xkb_extension(&m_connection, 1, 0, XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, _, _, _, _))
|
||||
.WillByDefault(DoAll(
|
||||
SetArgPointee<6>(s_xkbEventCode), // Set the "base_event_out" argument to the xkbEventCode, the value to identify XKB events
|
||||
Return(1)
|
||||
));
|
||||
|
||||
constexpr unsigned int xcbXkbSelectEventsSequence = 342;
|
||||
ON_CALL(m_interface, xcb_xkb_select_events(&m_connection, _, _, _, _, _, _, _))
|
||||
.WillByDefault(Return(xcb_void_cookie_t{/*.sequence = */ xcbXkbSelectEventsSequence}));
|
||||
ON_CALL(m_interface, xcb_request_check(&m_connection, testing::Field(&xcb_void_cookie_t::sequence, testing::Eq(xcbXkbSelectEventsSequence))))
|
||||
.WillByDefault(Return(nullptr)); // indicates success
|
||||
|
||||
ON_CALL(m_interface, xkb_x11_get_core_keyboard_device_id(&m_connection))
|
||||
.WillByDefault(Return(s_coreDeviceId));
|
||||
|
||||
ON_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForAKey))
|
||||
.WillByDefault(Return(XKB_KEY_a))
|
||||
;
|
||||
ON_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForShiftLKey))
|
||||
.WillByDefault(Return(XKB_KEY_Shift_L))
|
||||
;
|
||||
|
||||
ON_CALL(m_interface, xkb_state_update_mask(&m_xkbState, _, _, _, _, _, _))
|
||||
.WillByDefault(testing::Invoke(this, &XcbInputDeviceKeyboardTests::UpdateStateMask));
|
||||
|
||||
ON_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForAKey, nullptr, 0))
|
||||
.WillByDefault(Return(1));
|
||||
ON_CALL(m_interface, xkb_state_key_get_utf8(m_matchesStateWithoutShift, s_keycodeForAKey, _, 2))
|
||||
.WillByDefault(DoAll(
|
||||
SetArgPointee<2>('a'),
|
||||
Return(1)
|
||||
));
|
||||
ON_CALL(m_interface, xkb_state_key_get_utf8(m_matchesStateWithShift, s_keycodeForAKey, _, 2))
|
||||
.WillByDefault(DoAll(
|
||||
SetArgPointee<2>('A'),
|
||||
Return(1)
|
||||
));
|
||||
|
||||
ON_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForShiftLKey, nullptr, 0))
|
||||
.WillByDefault(Return(0));
|
||||
}
|
||||
|
||||
private:
|
||||
xkb_state_component UpdateStateMask(
|
||||
xkb_state* state,
|
||||
xkb_mod_mask_t depressed_mods,
|
||||
xkb_mod_mask_t latched_mods,
|
||||
xkb_mod_mask_t locked_mods,
|
||||
xkb_layout_index_t depressed_layout,
|
||||
xkb_layout_index_t latched_layout,
|
||||
xkb_layout_index_t locked_layout)
|
||||
{
|
||||
state->m_modifiers = depressed_mods | locked_mods;
|
||||
return {};
|
||||
}
|
||||
|
||||
protected:
|
||||
xkb_context m_xkbContext{};
|
||||
xkb_keymap m_xkbKeymap{};
|
||||
xkb_state m_xkbState{};
|
||||
const testing::Matcher<xkb_state*> m_matchesStateWithoutShift = testing::AllOf(&m_xkbState, testing::Field(&xkb_state::m_modifiers, 0));
|
||||
const testing::Matcher<xkb_state*> m_matchesStateWithShift = testing::AllOf(&m_xkbState, testing::Field(&xkb_state::m_modifiers, XCB_MOD_MASK_SHIFT));
|
||||
|
||||
static constexpr int32_t s_coreDeviceId{1};
|
||||
static constexpr uint8_t s_xkbEventCode{85};
|
||||
|
||||
static constexpr xcb_keycode_t s_keycodeForAKey{38};
|
||||
static constexpr xcb_keycode_t s_keycodeForShiftLKey{50};
|
||||
};
|
||||
|
||||
class InputTextNotificationListener
|
||||
: public InputTextNotificationBus::Handler
|
||||
{
|
||||
public:
|
||||
InputTextNotificationListener()
|
||||
{
|
||||
BusConnect();
|
||||
}
|
||||
MOCK_METHOD2(OnInputTextEvent, void(const AZStd::string& /*textUTF8*/, bool& /*o_hasBeenConsumed*/));
|
||||
};
|
||||
|
||||
TEST_F(XcbInputDeviceKeyboardTests, InputChannelsUpdateStateFromXcbEvents)
|
||||
{
|
||||
using testing::DoAll;
|
||||
using testing::Eq;
|
||||
using testing::Return;
|
||||
using testing::SetArgPointee;
|
||||
using testing::_;
|
||||
MockXcbInterface interface;
|
||||
|
||||
xcb_connection_t connection{};
|
||||
xkb_context xkbContext{};
|
||||
xkb_keymap xkbKeymap{};
|
||||
xkb_state xkbState{};
|
||||
const int32_t coreDeviceId{1};
|
||||
|
||||
constexpr xcb_keycode_t keycodeForAKey = 38;
|
||||
|
||||
const AZStd::array events
|
||||
{
|
||||
MakeEvent(xcb_key_press_event_t{
|
||||
/*.response_type = */ XCB_KEY_PRESS,
|
||||
/*.detail = */ keycodeForAKey,
|
||||
/*.detail = */ s_keycodeForAKey,
|
||||
/*.sequence = */ 0,
|
||||
/*.time = */ 0,
|
||||
/*.root = */ 0,
|
||||
@@ -59,7 +163,7 @@ namespace AzFramework
|
||||
}),
|
||||
MakeEvent(xcb_key_release_event_t{
|
||||
/*.response_type = */ XCB_KEY_RELEASE,
|
||||
/*.detail = */ keycodeForAKey,
|
||||
/*.detail = */ s_keycodeForAKey,
|
||||
/*.sequence = */ 0,
|
||||
/*.time = */ 0,
|
||||
/*.root = */ 0,
|
||||
@@ -75,51 +179,20 @@ namespace AzFramework
|
||||
}),
|
||||
};
|
||||
|
||||
EXPECT_CALL(interface, xcb_connect(_, _))
|
||||
.WillOnce(Return(&connection));
|
||||
EXPECT_CALL(interface, xcb_disconnect(&connection))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_CALL(interface, xkb_context_new(XKB_CONTEXT_NO_FLAGS))
|
||||
.WillOnce(Return(&xkbContext));
|
||||
EXPECT_CALL(interface, xkb_context_unref(&xkbContext))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_CALL(interface, xkb_x11_keymap_new_from_device(&xkbContext, &connection, coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS))
|
||||
.WillOnce(Return(&xkbKeymap));
|
||||
EXPECT_CALL(interface, xkb_keymap_unref(&xkbKeymap))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_CALL(interface, xkb_x11_state_new_from_device(&xkbKeymap, &connection, coreDeviceId))
|
||||
.WillOnce(Return(&xkbState));
|
||||
EXPECT_CALL(interface, xkb_state_unref(&xkbState))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_CALL(interface, xcb_xkb_use_extension(&connection, 1, 0));
|
||||
EXPECT_CALL(interface, xcb_xkb_use_extension_reply(&connection, _, _))
|
||||
.WillOnce(ReturnMalloc<xcb_xkb_use_extension_reply_t>(
|
||||
/* .response_type =*/static_cast<uint8_t>(XCB_XKB_USE_EXTENSION),
|
||||
/* .supported =*/ static_cast<uint8_t>(1))
|
||||
);
|
||||
EXPECT_CALL(interface, xkb_x11_get_core_keyboard_device_id(&connection))
|
||||
.WillRepeatedly(Return(coreDeviceId));
|
||||
|
||||
// Set the expectations for the events that will be generated
|
||||
// nullptr entries represent when the event queue is empty, and will cause
|
||||
// PumpSystemEventLoopUntilEmpty to return
|
||||
// event pointers are freed by the calling code, so we malloc new copies
|
||||
// here
|
||||
EXPECT_CALL(interface, xcb_poll_for_event(&connection))
|
||||
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[0]))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[1]))
|
||||
.WillOnce(Return(nullptr))
|
||||
;
|
||||
|
||||
EXPECT_CALL(interface, xkb_state_key_get_one_sym(&xkbState, keycodeForAKey))
|
||||
.WillOnce(Return(XKB_KEY_a))
|
||||
.WillOnce(Return(XKB_KEY_a))
|
||||
;
|
||||
EXPECT_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForAKey))
|
||||
.Times(2);
|
||||
|
||||
Application application;
|
||||
application.Start({}, {});
|
||||
@@ -142,4 +215,219 @@ namespace AzFramework
|
||||
|
||||
application.Stop();
|
||||
}
|
||||
|
||||
TEST_F(XcbInputDeviceKeyboardTests, TextEnteredFromXcbKeyPressEvents)
|
||||
{
|
||||
using testing::DoAll;
|
||||
using testing::Eq;
|
||||
using testing::Return;
|
||||
using testing::SetArgPointee;
|
||||
using testing::_;
|
||||
|
||||
// press a
|
||||
// release a
|
||||
// press shift
|
||||
// press a
|
||||
// release a
|
||||
// release shift
|
||||
const AZStd::array events
|
||||
{
|
||||
MakeEvent(xcb_key_press_event_t{
|
||||
/*.response_type = */ XCB_KEY_PRESS,
|
||||
/*.detail = */ s_keycodeForAKey,
|
||||
/*.sequence = */ 0,
|
||||
/*.time = */ 0,
|
||||
/*.root = */ 0,
|
||||
/*.event = */ 0,
|
||||
/*.child = */ 0,
|
||||
/*.root_x = */ 0,
|
||||
/*.root_y = */ 0,
|
||||
/*.event_x = */ 0,
|
||||
/*.event_y = */ 0,
|
||||
/*.state = */ 0,
|
||||
/*.same_screen = */ 0,
|
||||
/*.pad0 = */ 0
|
||||
}),
|
||||
MakeEvent(xcb_key_release_event_t{
|
||||
/*.response_type = */ XCB_KEY_RELEASE,
|
||||
/*.detail = */ s_keycodeForAKey,
|
||||
/*.sequence = */ 0,
|
||||
/*.time = */ 0,
|
||||
/*.root = */ 0,
|
||||
/*.event = */ 0,
|
||||
/*.child = */ 0,
|
||||
/*.root_x = */ 0,
|
||||
/*.root_y = */ 0,
|
||||
/*.event_x = */ 0,
|
||||
/*.event_y = */ 0,
|
||||
/*.state = */ 0,
|
||||
/*.same_screen = */ 0,
|
||||
/*.pad0 = */ 0
|
||||
}),
|
||||
// Pressing a modifier key will generate a key press event followed
|
||||
// by a state notify event
|
||||
MakeEvent(xcb_key_press_event_t{
|
||||
/*.response_type = */ XCB_KEY_PRESS,
|
||||
/*.detail = */ s_keycodeForShiftLKey,
|
||||
/*.sequence = */ 0,
|
||||
/*.time = */ 0,
|
||||
/*.root = */ 0,
|
||||
/*.event = */ 0,
|
||||
/*.child = */ 0,
|
||||
/*.root_x = */ 0,
|
||||
/*.root_y = */ 0,
|
||||
/*.event_x = */ 0,
|
||||
/*.event_y = */ 0,
|
||||
/*.state = */ 0,
|
||||
/*.same_screen = */ 0,
|
||||
/*.pad0 = */ 0
|
||||
}),
|
||||
MakeEvent(xcb_xkb_state_notify_event_t{
|
||||
/*.response_type = */ s_xkbEventCode,
|
||||
/*.xkbType = */ XCB_XKB_STATE_NOTIFY,
|
||||
/*.sequence = */ 0,
|
||||
/*.time = */ 0,
|
||||
/*.deviceID = */ s_coreDeviceId,
|
||||
/*.mods = */ XCB_MOD_MASK_SHIFT,
|
||||
/*.baseMods = */ XCB_MOD_MASK_SHIFT,
|
||||
/*.latchedMods = */ 0,
|
||||
/*.lockedMods = */ 0,
|
||||
/*.group = */ 0,
|
||||
/*.baseGroup = */ 0,
|
||||
/*.latchedGroup = */ 0,
|
||||
/*.lockedGroup = */ 0,
|
||||
/*.compatState = */ XCB_MOD_MASK_SHIFT,
|
||||
/*.grabMods = */ XCB_MOD_MASK_SHIFT,
|
||||
/*.compatGrabMods = */ XCB_MOD_MASK_SHIFT,
|
||||
/*.lookupMods = */ XCB_MOD_MASK_SHIFT,
|
||||
/*.compatLoockupMods = */ XCB_MOD_MASK_SHIFT,
|
||||
/*.ptrBtnState = */ 0,
|
||||
/*.changed = */ 0,
|
||||
/*.keycode = */ s_keycodeForShiftLKey,
|
||||
/*.eventType = */ XCB_KEY_PRESS,
|
||||
/*.requestMajor = */ 0,
|
||||
/*.requestMinor = */ 0,
|
||||
}),
|
||||
MakeEvent(xcb_key_press_event_t{
|
||||
/*.response_type = */ XCB_KEY_PRESS,
|
||||
/*.detail = */ s_keycodeForAKey,
|
||||
/*.sequence = */ 0,
|
||||
/*.time = */ 0,
|
||||
/*.root = */ 0,
|
||||
/*.event = */ 0,
|
||||
/*.child = */ 0,
|
||||
/*.root_x = */ 0,
|
||||
/*.root_y = */ 0,
|
||||
/*.event_x = */ 0,
|
||||
/*.event_y = */ 0,
|
||||
/*.state = */ 0,
|
||||
/*.same_screen = */ 0,
|
||||
/*.pad0 = */ 0
|
||||
}),
|
||||
MakeEvent(xcb_key_release_event_t{
|
||||
/*.response_type = */ XCB_KEY_RELEASE,
|
||||
/*.detail = */ s_keycodeForAKey,
|
||||
/*.sequence = */ 0,
|
||||
/*.time = */ 0,
|
||||
/*.root = */ 0,
|
||||
/*.event = */ 0,
|
||||
/*.child = */ 0,
|
||||
/*.root_x = */ 0,
|
||||
/*.root_y = */ 0,
|
||||
/*.event_x = */ 0,
|
||||
/*.event_y = */ 0,
|
||||
/*.state = */ 0,
|
||||
/*.same_screen = */ 0,
|
||||
/*.pad0 = */ 0
|
||||
}),
|
||||
MakeEvent(xcb_key_release_event_t{
|
||||
/*.response_type = */ XCB_KEY_RELEASE,
|
||||
/*.detail = */ s_keycodeForShiftLKey,
|
||||
/*.sequence = */ 0,
|
||||
/*.time = */ 0,
|
||||
/*.root = */ 0,
|
||||
/*.event = */ 0,
|
||||
/*.child = */ 0,
|
||||
/*.root_x = */ 0,
|
||||
/*.root_y = */ 0,
|
||||
/*.event_x = */ 0,
|
||||
/*.event_y = */ 0,
|
||||
/*.state = */ 0,
|
||||
/*.same_screen = */ 0,
|
||||
/*.pad0 = */ 0
|
||||
}),
|
||||
MakeEvent(xcb_xkb_state_notify_event_t{
|
||||
/*.response_type = */ s_xkbEventCode,
|
||||
/*.xkbType = */ XCB_XKB_STATE_NOTIFY,
|
||||
/*.sequence = */ 0,
|
||||
/*.time = */ 0,
|
||||
/*.deviceID = */ s_coreDeviceId,
|
||||
/*.mods = */ 0,
|
||||
/*.baseMods = */ 0,
|
||||
/*.latchedMods = */ 0,
|
||||
/*.lockedMods = */ 0,
|
||||
/*.group = */ 0,
|
||||
/*.baseGroup = */ 0,
|
||||
/*.latchedGroup = */ 0,
|
||||
/*.lockedGroup = */ 0,
|
||||
/*.compatState = */ 0,
|
||||
/*.grabMods = */ 0,
|
||||
/*.compatGrabMods = */ 0,
|
||||
/*.lookupMods = */ 0,
|
||||
/*.compatLoockupMods = */ 0,
|
||||
/*.ptrBtnState = */ 0,
|
||||
/*.changed = */ 0,
|
||||
/*.keycode = */ s_keycodeForShiftLKey,
|
||||
/*.eventType = */ XCB_KEY_RELEASE,
|
||||
/*.requestMajor = */ 0,
|
||||
/*.requestMinor = */ 0,
|
||||
}),
|
||||
};
|
||||
|
||||
// Set the expectations for the events that will be generated
|
||||
// nullptr entries represent when the event queue is empty, and will cause
|
||||
// PumpSystemEventLoopUntilEmpty to return
|
||||
// event pointers are freed by the calling code, so we malloc new copies
|
||||
// here
|
||||
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[0])) // press a
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[1])) // release a
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[2])) // press shift
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[3])) // state notify shift is down
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[4])) // press a
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[5])) // release a
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[6])) // release shift
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[7])) // state notify shift is up
|
||||
.WillRepeatedly(Return(nullptr))
|
||||
;
|
||||
|
||||
EXPECT_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForAKey, nullptr, 0))
|
||||
.Times(2);
|
||||
EXPECT_CALL(m_interface, xkb_state_key_get_utf8(m_matchesStateWithoutShift, s_keycodeForAKey, _, 2))
|
||||
.Times(1);
|
||||
EXPECT_CALL(m_interface, xkb_state_key_get_utf8(m_matchesStateWithShift, s_keycodeForAKey, _, 2))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForShiftLKey, nullptr, 0))
|
||||
.Times(1);
|
||||
|
||||
InputTextNotificationListener textListener;
|
||||
EXPECT_CALL(textListener, OnInputTextEvent(StrEq("a"), _)).Times(1);
|
||||
EXPECT_CALL(textListener, OnInputTextEvent(StrEq("A"), _)).Times(1);
|
||||
|
||||
Application application;
|
||||
application.Start({}, {});
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
application.PumpSystemEventLoopUntilEmpty();
|
||||
application.TickSystem();
|
||||
application.Tick();
|
||||
}
|
||||
|
||||
application.Stop();
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -9,9 +9,12 @@
|
||||
set(FILES
|
||||
Actions.h
|
||||
Main.cpp
|
||||
Matchers.h
|
||||
MockXcbInterface.cpp
|
||||
MockXcbInterface.h
|
||||
Printers.cpp
|
||||
Printers.h
|
||||
XcbBaseTestFixture.cpp
|
||||
XcbBaseTestFixture.h
|
||||
XcbInputDeviceKeyboardTests.cpp
|
||||
)
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzQtComponents/AzQtComponents_Traits_Platform.h>
|
||||
#include <AzQtComponents/Components/Widgets/FileDialog.h>
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
|
||||
namespace AzQtComponents
|
||||
{
|
||||
@@ -24,7 +25,12 @@ namespace AzQtComponents
|
||||
// Trigger Qt's save filename dialog
|
||||
// If filePath isn't empty, it means we are prompting again because the filename was invalid,
|
||||
// so pass it instead of the directory so the filename is pre-filled in for the user
|
||||
filePath = QFileDialog::getSaveFileName(parent, caption, (filePath.isEmpty()) ? dir : filePath, filter, selectedFilter, options);
|
||||
QString localSelectedFilter;
|
||||
filePath = QFileDialog::getSaveFileName(parent, caption, (filePath.isEmpty()) ? dir : filePath, filter, &localSelectedFilter, options);
|
||||
if (selectedFilter)
|
||||
{
|
||||
*selectedFilter = localSelectedFilter;
|
||||
}
|
||||
|
||||
if (!filePath.isEmpty())
|
||||
{
|
||||
@@ -32,15 +38,39 @@ namespace AzQtComponents
|
||||
QString fileName = fileInfo.fileName();
|
||||
|
||||
// Check if the filename has any invalid characters
|
||||
QRegExp validFileNameRegex("^[a-zA-Z0-9_\\-./]*$");
|
||||
shouldPromptAgain = !validFileNameRegex.exactMatch(fileName);
|
||||
QRegularExpression validFileNameRegex("^[a-zA-Z0-9_\\-./]*$");
|
||||
QRegularExpressionMatch validFileNameMatch = validFileNameRegex.match(fileName);
|
||||
|
||||
// If the filename had invalid characters, then show a warning message and then we will re-prompt the save filename dialog
|
||||
if (shouldPromptAgain)
|
||||
if (!validFileNameMatch.hasMatch())
|
||||
{
|
||||
QMessageBox::warning(parent, QObject::tr("Invalid filename"),
|
||||
QObject::tr("O3DE assets are restricted to alphanumeric characters, hyphens (-), underscores (_), and dots (.)\n\n%1").arg(fileName));
|
||||
shouldPromptAgain = true;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldPromptAgain = false;
|
||||
}
|
||||
#if AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_APPLY_MISSING_EXTENSION
|
||||
// If a filter was selected, then make sure that the resulting filename ends with that extension. On systems that use the default QFileDialog,
|
||||
// the extension is not guaranteed to be set in the resulting filename
|
||||
if (FileDialog::ApplyMissingExtension(localSelectedFilter, filePath))
|
||||
{
|
||||
// If an extension had to be applied, then the file dialog did not handle the case of overwriting existing files.
|
||||
// We need to check that condition before we proceed
|
||||
QFileInfo updatedFilePath(filePath);
|
||||
|
||||
if (updatedFilePath.exists())
|
||||
{
|
||||
QMessageBox::StandardButton overwriteSelection = QMessageBox::question(parent,
|
||||
QObject::tr("File exists"),
|
||||
QObject::tr("%1 exists. Do you want to overwrite the existing file?").arg(updatedFilePath.fileName()));
|
||||
shouldPromptAgain = (overwriteSelection == QMessageBox::No);
|
||||
}
|
||||
}
|
||||
#endif // AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_APPLY_MISSING_EXTENSION
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -51,4 +81,56 @@ namespace AzQtComponents
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
bool FileDialog::ApplyMissingExtension(const QString& selectedFilter, QString& filePath)
|
||||
{
|
||||
if (selectedFilter.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// According to the QT documentation for QFileDialog, the selected filter will come in the form
|
||||
// <Filter Name> (<filter pattern1> <filter pattern2> .. <filter patternN> )
|
||||
//
|
||||
// For example:
|
||||
// "Images (*.gif *.png *.jpg)"
|
||||
//
|
||||
// Extract the contents of the <filter pattern>(s) inside the parenthesis and split them based on a whitespace or comma
|
||||
const QRegularExpression filterContent(".*\\((?<filters>[^\\)]+)\\)");
|
||||
QRegularExpressionMatch filterContentMatch = filterContent.match(selectedFilter);
|
||||
if (!filterContentMatch.hasMatch())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
QString filterExtensionsString = filterContentMatch.captured("filters");
|
||||
QStringList filterExtensionsFull = filterExtensionsString.split(" ", Qt::SkipEmptyParts);
|
||||
if (filterExtensionsFull.length() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there are multiple suffixes in the selected filter, then default to the first one if a suffix needs to be appended
|
||||
QString defaultSuffix = filterExtensionsFull[0].mid(1);
|
||||
|
||||
// Iterate through the filter patterns to see if the current filename matches
|
||||
QFileInfo fileInfo(filePath);
|
||||
bool extensionNeeded = true;
|
||||
for (const QString& filterExtensionFull : filterExtensionsFull)
|
||||
{
|
||||
QString wildcardExpression = QRegularExpression::wildcardToRegularExpression(filterExtensionFull);
|
||||
QRegularExpression filterPattern(wildcardExpression, AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_FILTER_CASE_SENSITIVITY);
|
||||
QRegularExpressionMatch filterPatternMatch = filterPattern.match(fileInfo.fileName());
|
||||
if (filterPatternMatch.hasMatch())
|
||||
{
|
||||
// The filename matches one of the filter patterns already, the extension does not need to be added to the filename
|
||||
extensionNeeded = false;
|
||||
}
|
||||
}
|
||||
if (extensionNeeded)
|
||||
{
|
||||
// If the current (if any) suffix does not match, automatically add the default suffix for the selected filter
|
||||
filePath.append(defaultSuffix);
|
||||
}
|
||||
return extensionNeeded;
|
||||
}
|
||||
} // namespace AzQtComponents
|
||||
|
||||
@@ -24,6 +24,12 @@ namespace AzQtComponents
|
||||
static QString GetSaveFileName(QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
const QString& dir = QString(), const QString& filter = QString(),
|
||||
QString* selectedFilter = nullptr, QFileDialog::Options options = QFileDialog::Options());
|
||||
|
||||
//! Helper method that parses a selected filter from Qt's QFileDialog::getSaveFileName and applies the
|
||||
//! selected filter's extension to the filePath if it doesnt already have the extension. This is needed
|
||||
//! on platforms that do not have a default file dialog (These platforms uses Qt's custom file dialog which will
|
||||
//! not apply the filter's extension automatically on user entered filenames)
|
||||
static bool ApplyMissingExtension(const QString& selectedFilter, QString& filePath);
|
||||
};
|
||||
|
||||
} // namespace AzQtComponents
|
||||
|
||||
@@ -12,4 +12,6 @@ set(FILES
|
||||
../../Utilities/QtWindowUtilities_linux.cpp
|
||||
../../Utilities/ScreenGrabber_linux.cpp
|
||||
../../../Platform/Linux/AzQtComponents/Components/StyledDockWidget_Linux.cpp
|
||||
../../../Platform/Linux/AzQtComponents/AzQtComponents_Traits_Linux.h
|
||||
../../../Platform/Linux/AzQtComponents/AzQtComponents_Traits_Platform.h
|
||||
)
|
||||
|
||||
@@ -12,4 +12,6 @@ set(FILES
|
||||
../../Utilities/QtWindowUtilities_mac.mm
|
||||
../../Utilities/ScreenGrabber_mac.mm
|
||||
../../../Platform/Mac/AzQtComponents/Components/StyledDockWidget_Mac.cpp
|
||||
../../../Platform/Mac/AzQtComponents/AzQtComponents_Traits_Mac.h
|
||||
../../../Platform/Mac/AzQtComponents/AzQtComponents_Traits_Platform.h
|
||||
)
|
||||
|
||||
+2
@@ -17,4 +17,6 @@ set(FILES
|
||||
../../Components/TitleBarOverdrawScreenHandler_win.h
|
||||
../../Components/TitleBarOverdrawScreenHandler_win.cpp
|
||||
../../../Platform/Windows/AzQtComponents/Components/StyledDockWidget_Windows.cpp
|
||||
../../../Platform/Windows/AzQtComponents/AzQtComponents_Traits_Windows.h
|
||||
../../../Platform/Windows/AzQtComponents/AzQtComponents_Traits_Platform.h
|
||||
)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzQtComponents/Components/Widgets/FileDialog.h>
|
||||
#include <QString>
|
||||
|
||||
TEST(AzQtComponents, ApplyMissingExtension_UpdateMissingExtension_Success)
|
||||
{
|
||||
const QString textFiler{"Text Files (*.txt)"};
|
||||
QString testPath{"testFile"};
|
||||
bool result = AzQtComponents::FileDialog::ApplyMissingExtension(textFiler, testPath);
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_STRCASEEQ("testFile.txt", testPath.toUtf8().constData());
|
||||
}
|
||||
|
||||
TEST(AzQtComponents, ApplyMissingExtension_NoUpdateExistingExtension_Success)
|
||||
{
|
||||
const QString textFiler{"Text Files (*.txt)"};
|
||||
QString testPath{"testFile.txt"};
|
||||
bool result = AzQtComponents::FileDialog::ApplyMissingExtension(textFiler, testPath);
|
||||
EXPECT_FALSE(result);
|
||||
EXPECT_STRCASEEQ("testFile.txt", testPath.toUtf8().constData());
|
||||
}
|
||||
|
||||
TEST(AzQtComponents, ApplyMissingExtension_UpdateMissingExtensionMultipleExtensionFilter_Success)
|
||||
{
|
||||
const QString textFiler{"Image Files (*.jpg *.bmp *.png)"};
|
||||
QString testPath{"testFile"};
|
||||
bool result = AzQtComponents::FileDialog::ApplyMissingExtension(textFiler, testPath);
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_STRCASEEQ("testFile.jpg", testPath.toUtf8().constData());
|
||||
}
|
||||
|
||||
TEST(AzQtComponents, ApplyMissingExtension_NoUpdateMissingExtensionMultipleExtensionFilter_Success)
|
||||
{
|
||||
const QString textFiler{"Image Files (*.jpg *.bmp *.png)"};
|
||||
QString testPath{"testFile.png"};
|
||||
bool result = AzQtComponents::FileDialog::ApplyMissingExtension(textFiler, testPath);
|
||||
EXPECT_FALSE(result);
|
||||
EXPECT_STRCASEEQ("testFile.png", testPath.toUtf8().constData());
|
||||
}
|
||||
|
||||
TEST(AzQtComponents, ApplyMissingExtension_NoUpdateMissingExtensionEmptyFilter_Success)
|
||||
{
|
||||
const QString textFiler{""};
|
||||
QString testPath{"testFile"};
|
||||
bool result = AzQtComponents::FileDialog::ApplyMissingExtension(textFiler, testPath);
|
||||
EXPECT_FALSE(result);
|
||||
EXPECT_STRCASEEQ("testFile", testPath.toUtf8().constData());
|
||||
}
|
||||
|
||||
TEST(AzQtComponents, ApplyMissingExtension_NoUpdateMissingExtensionInvalidFilter_Success)
|
||||
{
|
||||
const QString textFiler{"Bad Filter!!"};
|
||||
QString testPath{"testFile"};
|
||||
bool result = AzQtComponents::FileDialog::ApplyMissingExtension(textFiler, testPath);
|
||||
EXPECT_FALSE(result);
|
||||
EXPECT_STRCASEEQ("testFile", testPath.toUtf8().constData());
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
set(FILES
|
||||
Tests/AzQtComponentTests.cpp
|
||||
Tests/ColorControllerTests.cpp
|
||||
Tests/FileDialogTests.cpp
|
||||
Tests/FloatToStringConversionTests.cpp
|
||||
Tests/HexParsingTests.cpp
|
||||
Tests/StyleSheetCacheTests.cpp
|
||||
|
||||
@@ -10,6 +10,8 @@ if(NOT PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
|
||||
|
||||
ly_add_target(
|
||||
NAME AzQtComponents SHARED
|
||||
NAMESPACE AZ
|
||||
@@ -26,6 +28,7 @@ ly_add_target(
|
||||
AzQtComponents
|
||||
PUBLIC
|
||||
.
|
||||
${pal_dir}
|
||||
COMPILE_DEFINITIONS
|
||||
PRIVATE
|
||||
AZ_QT_COMPONENTS_EXPORT_SYMBOLS
|
||||
@@ -53,6 +56,7 @@ ly_add_target(
|
||||
.
|
||||
AzQtComponents
|
||||
AzQtComponents/Gallery
|
||||
${pal_dir}
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
3rdParty::Qt::Svg
|
||||
@@ -86,6 +90,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
PRIVATE
|
||||
Tests
|
||||
AzQtComponents
|
||||
${pal_dir}
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzQtComponents
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_APPLY_MISSING_EXTENSION 1
|
||||
#define AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_FILTER_CASE_SENSITIVITY QRegularExpression::NoPatternOption
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzQtComponents/AzQtComponents_Traits_Linux.h>
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_APPLY_MISSING_EXTENSION 0
|
||||
#define AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_FILTER_CASE_SENSITIVITY QRegularExpression::NoPatternOption
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzQtComponents/AzQtComponents_Traits_Mac.h>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzQtComponents/AzQtComponents_Traits_Windows.h>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_APPLY_MISSING_EXTENSION 0
|
||||
#define AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_FILTER_CASE_SENSITIVITY QRegularExpression::CaseInsensitiveOption
|
||||
@@ -69,6 +69,7 @@
|
||||
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiSystemComponent.h>
|
||||
#include <AzToolsFramework/Undo/UndoCacheInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
|
||||
#include <Entity/EntityUtilityComponent.h>
|
||||
|
||||
#include <QtWidgets/QMessageBox>
|
||||
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QFileInfo::d_ptr': class 'QSharedDataPointer<QFileInfoPrivate>' needs to have dll-interface to be used by clients of class 'QFileInfo'
|
||||
@@ -271,7 +272,8 @@ namespace AzToolsFramework
|
||||
azrtti_typeid<AzToolsFramework::EditorInteractionSystemComponent>(),
|
||||
azrtti_typeid<Components::EditorEntitySearchComponent>(),
|
||||
azrtti_typeid<Components::EditorIntersectorComponent>(),
|
||||
azrtti_typeid<AzToolsFramework::SliceRequestComponent>()
|
||||
azrtti_typeid<AzToolsFramework::SliceRequestComponent>(),
|
||||
azrtti_typeid<AzToolsFramework::EntityUtilityComponent>()
|
||||
});
|
||||
|
||||
return components;
|
||||
|
||||
@@ -410,7 +410,7 @@ namespace AzToolsFramework
|
||||
filter.append(ext);
|
||||
if (i < n - 1)
|
||||
{
|
||||
filter.append(", ");
|
||||
filter.append(" ");
|
||||
}
|
||||
}
|
||||
filter.append(")");
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
#include <AzToolsFramework/Thumbnails/ThumbnailerNullComponent.h>
|
||||
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemComponent.h>
|
||||
#include <AzToolsFramework/Entity/EntityUtilityComponent.h>
|
||||
|
||||
AZ_DEFINE_BUDGET(AzToolsFramework);
|
||||
|
||||
@@ -71,6 +72,7 @@ namespace AzToolsFramework
|
||||
Components::EditorSelectionAccentSystemComponent::CreateDescriptor(),
|
||||
EditorEntityContextComponent::CreateDescriptor(),
|
||||
EditorEntityFixupComponent::CreateDescriptor(),
|
||||
EntityUtilityComponent::CreateDescriptor(),
|
||||
ContainerEntitySystemComponent::CreateDescriptor(),
|
||||
FocusModeSystemComponent::CreateDescriptor(),
|
||||
SliceMetadataEntityContextComponent::CreateDescriptor(),
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
#include <AzCore/JSON/rapidjson.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerializationSettings.h>
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
#include <AzFramework/Entity/EntityContext.h>
|
||||
#include <AzFramework/FileFunc/FileFunc.h>
|
||||
#include <AzToolsFramework/Entity/EntityUtilityComponent.h>
|
||||
#include <Entity/EditorEntityContextBus.h>
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
void ComponentDetails::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<ComponentDetails>()
|
||||
->Field("TypeInfo", &ComponentDetails::m_typeInfo)
|
||||
->Field("BaseClasses", &ComponentDetails::m_baseClasses);
|
||||
|
||||
serializeContext->RegisterGenericType<AZStd::vector<ComponentDetails>>();
|
||||
}
|
||||
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->Class<ComponentDetails>()
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "entity")
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Property("TypeInfo", BehaviorValueProperty(&ComponentDetails::m_typeInfo))
|
||||
->Property("BaseClasses", BehaviorValueProperty(&ComponentDetails::m_baseClasses))
|
||||
->Method("__repr__", [](const ComponentDetails& obj)
|
||||
{
|
||||
std::ostringstream result;
|
||||
bool first = true;
|
||||
|
||||
for (const auto& baseClass : obj.m_baseClasses)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
result << ", ";
|
||||
}
|
||||
|
||||
first = false;
|
||||
result << baseClass.c_str();
|
||||
}
|
||||
|
||||
return AZStd::string::format("%s, Base Classes: <%s>", obj.m_typeInfo.c_str(), result.str().c_str());
|
||||
})
|
||||
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString);
|
||||
}
|
||||
}
|
||||
|
||||
AZ::EntityId EntityUtilityComponent::CreateEditorReadyEntity(const AZStd::string& entityName)
|
||||
{
|
||||
auto* newEntity = m_entityContext->CreateEntity(entityName.c_str());
|
||||
|
||||
if (!newEntity)
|
||||
{
|
||||
AZ_Error("EditorEntityUtility", false, "Failed to create new entity %s", entityName.c_str());
|
||||
return AZ::EntityId();
|
||||
}
|
||||
|
||||
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
|
||||
&AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, *newEntity);
|
||||
|
||||
newEntity->Init();
|
||||
auto newEntityId = newEntity->GetId();
|
||||
|
||||
m_createdEntities.emplace_back(newEntityId);
|
||||
|
||||
return newEntityId;
|
||||
}
|
||||
|
||||
AZ::TypeId GetComponentTypeIdFromName(const AZStd::string& typeName)
|
||||
{
|
||||
// Try to create a TypeId first. We won't show any warnings if this fails as the input might be a class name instead
|
||||
AZ::TypeId typeId = AZ::TypeId::CreateStringPermissive(typeName.data());
|
||||
|
||||
// If the typeId is null, try a lookup by class name
|
||||
if (typeId.IsNull())
|
||||
{
|
||||
AZ::SerializeContext* serializeContext = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
||||
|
||||
auto typeNameCrc = AZ::Crc32(typeName.data());
|
||||
auto typeUuidList = serializeContext->FindClassId(typeNameCrc);
|
||||
|
||||
// TypeId is invalid or class name is invalid
|
||||
if (typeUuidList.empty())
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "Provided type %s is either an invalid TypeId or does not match any class names", typeName.c_str());
|
||||
return AZ::TypeId::CreateNull();
|
||||
}
|
||||
|
||||
typeId = typeUuidList[0];
|
||||
}
|
||||
|
||||
return typeId;
|
||||
}
|
||||
|
||||
AZ::Component* FindComponentHelper(AZ::EntityId entityId, const AZ::TypeId& typeId, AZ::ComponentId componentId, bool createComponent = false)
|
||||
{
|
||||
AZ::Entity* entity = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
|
||||
|
||||
if (!entity)
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "Invalid entityId %s", entityId.ToString().c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AZ::Component* component = nullptr;
|
||||
if (componentId != AZ::InvalidComponentId)
|
||||
{
|
||||
component = entity->FindComponent(componentId);
|
||||
}
|
||||
else
|
||||
{
|
||||
component = entity->FindComponent(typeId);
|
||||
}
|
||||
|
||||
if (!component && createComponent)
|
||||
{
|
||||
component = entity->CreateComponent(typeId);
|
||||
}
|
||||
|
||||
if (!component)
|
||||
{
|
||||
AZ_Error(
|
||||
"EntityUtilityComponent", false, "Failed to find component (%s) on entity %s (%s)",
|
||||
componentId != AZ::InvalidComponentId ? AZStd::to_string(componentId).c_str()
|
||||
: typeId.ToString<AZStd::string>().c_str(),
|
||||
entityId.ToString().c_str(),
|
||||
entity->GetName().c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
AzFramework::BehaviorComponentId EntityUtilityComponent::GetOrAddComponentByTypeName(AZ::EntityId entityId, const AZStd::string& typeName)
|
||||
{
|
||||
AZ::TypeId typeId = GetComponentTypeIdFromName(typeName);
|
||||
|
||||
if (typeId.IsNull())
|
||||
{
|
||||
return AzFramework::BehaviorComponentId(AZ::InvalidComponentId);
|
||||
}
|
||||
|
||||
AZ::Component* component = FindComponentHelper(entityId, typeId, AZ::InvalidComponentId, true);
|
||||
|
||||
return component ? AzFramework::BehaviorComponentId(component->GetId()) :
|
||||
AzFramework::BehaviorComponentId(AZ::InvalidComponentId);
|
||||
}
|
||||
|
||||
bool EntityUtilityComponent::UpdateComponentForEntity(AZ::EntityId entityId, AzFramework::BehaviorComponentId componentId, const AZStd::string& json)
|
||||
{
|
||||
if (!componentId.IsValid())
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "Invalid componentId passed to UpdateComponentForEntity");
|
||||
return false;
|
||||
}
|
||||
|
||||
AZ::Component* component = FindComponentHelper(entityId, AZ::TypeId::CreateNull(), componentId);
|
||||
|
||||
if (!component)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using namespace AZ::JsonSerializationResult;
|
||||
|
||||
AZ::JsonDeserializerSettings settings = AZ::JsonDeserializerSettings{};
|
||||
settings.m_reporting = []([[maybe_unused]] AZStd::string_view message, ResultCode result, AZStd::string_view) -> auto
|
||||
{
|
||||
if (result.GetProcessing() == Processing::Halted)
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "JSON %s\n", message.data());
|
||||
}
|
||||
else if (result.GetOutcome() > Outcomes::PartialDefaults)
|
||||
{
|
||||
AZ_Warning("EntityUtilityComponent", false, "JSON %s\n", message.data());
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
rapidjson::Document doc;
|
||||
doc.Parse<rapidjson::kParseCommentsFlag>(json.data(), json.size());
|
||||
ResultCode resultCode = AZ::JsonSerialization::Load(*component, doc, settings);
|
||||
|
||||
return resultCode.GetProcessing() != Processing::Halted;
|
||||
}
|
||||
|
||||
AZStd::string EntityUtilityComponent::GetComponentDefaultJson(const AZStd::string& typeName)
|
||||
{
|
||||
AZ::TypeId typeId = GetComponentTypeIdFromName(typeName);
|
||||
|
||||
if (typeId.IsNull())
|
||||
{
|
||||
// GetComponentTypeIdFromName already does error handling
|
||||
return "";
|
||||
}
|
||||
|
||||
AZ::SerializeContext* serializeContext = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
||||
|
||||
const AZ::SerializeContext::ClassData* classData = serializeContext->FindClassData(typeId);
|
||||
|
||||
if (!classData)
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "Failed to find ClassData for typeId %s (%s)", typeId.ToString<AZStd::string>().c_str(), typeName.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
void* component = classData->m_factory->Create("Component");
|
||||
rapidjson::Document document;
|
||||
AZ::JsonSerializerSettings settings;
|
||||
settings.m_keepDefaults = true;
|
||||
|
||||
auto resultCode = AZ::JsonSerialization::Store(document, document.GetAllocator(), component, nullptr, typeId, settings);
|
||||
|
||||
// Clean up the allocated component ASAP, we don't need it anymore
|
||||
classData->m_factory->Destroy(component);
|
||||
|
||||
if (resultCode.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "Failed to serialize component to json (%s): %s",
|
||||
typeName.c_str(), resultCode.ToString(typeName).c_str())
|
||||
return "";
|
||||
}
|
||||
|
||||
AZStd::string jsonString;
|
||||
AZ::Outcome<void, AZStd::string> outcome = AZ::JsonSerializationUtils::WriteJsonString(document, jsonString);
|
||||
|
||||
if (!outcome.IsSuccess())
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "Failed to write component json to string: %s", outcome.GetError().c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
AZStd::vector<ComponentDetails> EntityUtilityComponent::FindMatchingComponents(const AZStd::string& searchTerm)
|
||||
{
|
||||
AZ::SerializeContext* serializeContext = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
||||
|
||||
if (m_typeInfo.empty())
|
||||
{
|
||||
serializeContext->EnumerateDerived<AZ::Component>(
|
||||
[this, serializeContext](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid& /*typeId*/)
|
||||
{
|
||||
auto& typeInfo = m_typeInfo.emplace_back(classData->m_typeId, classData->m_name, AZStd::vector<AZStd::string>{});
|
||||
|
||||
serializeContext->EnumerateBase(
|
||||
[&typeInfo](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid&)
|
||||
{
|
||||
if (classData)
|
||||
{
|
||||
AZStd::get<2>(typeInfo).emplace_back(classData->m_name);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
classData->m_typeId);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
AZStd::vector<ComponentDetails> matches;
|
||||
|
||||
for (const auto& [typeId, typeName, baseClasses] : m_typeInfo)
|
||||
{
|
||||
if (AZStd::wildcard_match(searchTerm, typeName))
|
||||
{
|
||||
ComponentDetails details;
|
||||
details.m_typeInfo = AZStd::string::format("%s %s", typeId.ToString<AZStd::string>().c_str(), typeName.c_str());
|
||||
details.m_baseClasses = baseClasses;
|
||||
|
||||
matches.emplace_back(AZStd::move(details));
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
void EntityUtilityComponent::ResetEntityContext()
|
||||
{
|
||||
for (AZ::EntityId entityId : m_createdEntities)
|
||||
{
|
||||
m_entityContext->DestroyEntityById(entityId);
|
||||
}
|
||||
|
||||
m_createdEntities.clear();
|
||||
m_entityContext->ResetContext();
|
||||
}
|
||||
|
||||
void EntityUtilityComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
ComponentDetails::Reflect(context);
|
||||
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<EntityUtilityComponent, AZ::Component>();
|
||||
}
|
||||
|
||||
if (auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->ConstantProperty("InvalidComponentId", BehaviorConstant(AZ::InvalidComponentId))
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Category, "Entity")
|
||||
->Attribute(AZ::Script::Attributes::Module, "entity");
|
||||
|
||||
behaviorContext->EBus<EntityUtilityBus>("EntityUtilityBus")
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::Category, "Entity")
|
||||
->Attribute(AZ::Script::Attributes::Module, "entity")
|
||||
->Event("CreateEditorReadyEntity", &EntityUtilityBus::Events::CreateEditorReadyEntity)
|
||||
->Event("GetOrAddComponentByTypeName", &EntityUtilityBus::Events::GetOrAddComponentByTypeName)
|
||||
->Event("UpdateComponentForEntity", &EntityUtilityBus::Events::UpdateComponentForEntity)
|
||||
->Event("FindMatchingComponents", &EntityUtilityBus::Events::FindMatchingComponents)
|
||||
->Event("GetComponentDefaultJson", &EntityUtilityBus::Events::GetComponentDefaultJson)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
void EntityUtilityComponent::Activate()
|
||||
{
|
||||
m_entityContext = AZStd::make_unique<AzFramework::EntityContext>(UtilityEntityContextId);
|
||||
m_entityContext->InitContext();
|
||||
EntityUtilityBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void EntityUtilityComponent::Deactivate()
|
||||
{
|
||||
EntityUtilityBus::Handler::BusDisconnect();
|
||||
m_entityContext = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorDisabledCompositionBus.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorPendingCompositionBus.h>
|
||||
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
|
||||
#include <AzCore/Component/ComponentApplication.h>
|
||||
#include <AzFramework/Entity/BehaviorEntity.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
struct ComponentDetails
|
||||
{
|
||||
AZ_TYPE_INFO(AzToolsFramework::ComponentDetails, "{107D8379-4AD4-4547-BEE1-184B120F23E9}");
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
AZStd::string m_typeInfo;
|
||||
AZStd::vector<AZStd::string> m_baseClasses;
|
||||
};
|
||||
|
||||
// This ebus is intended to provide behavior-context friendly APIs to create and manage entities
|
||||
struct EntityUtilityTraits : AZ::EBusTraits
|
||||
{
|
||||
AZ_RTTI(AzToolsFramework::EntityUtilityTraits, "{A6305CAE-C825-43F9-A44D-E503910912AF}");
|
||||
|
||||
virtual ~EntityUtilityTraits() = default;
|
||||
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
|
||||
// Creates an entity with the default editor components attached and initializes the entity
|
||||
virtual AZ::EntityId CreateEditorReadyEntity(const AZStd::string& entityName) = 0;
|
||||
|
||||
virtual AzFramework::BehaviorComponentId GetOrAddComponentByTypeName(AZ::EntityId entity, const AZStd::string& typeName) = 0;
|
||||
|
||||
virtual bool UpdateComponentForEntity(AZ::EntityId entity, AzFramework::BehaviorComponentId component, const AZStd::string& json) = 0;
|
||||
|
||||
// Gets a JSON string containing describing the default serialization state of the specified component
|
||||
virtual AZStd::string GetComponentDefaultJson(const AZStd::string& typeName) = 0;
|
||||
|
||||
// Returns a list of matching component type names. Supports wildcard search terms
|
||||
virtual AZStd::vector<ComponentDetails> FindMatchingComponents(const AZStd::string& searchTerm) = 0;
|
||||
|
||||
virtual void ResetEntityContext() = 0;
|
||||
};
|
||||
|
||||
using EntityUtilityBus = AZ::EBus<EntityUtilityTraits>;
|
||||
|
||||
struct EntityUtilityComponent : AZ::Component
|
||||
, EntityUtilityBus::Handler
|
||||
{
|
||||
inline const static AZ::Uuid UtilityEntityContextId = AZ::Uuid("{9C277B88-E79E-4F8A-BAFF-A4C175BD565F}");
|
||||
|
||||
AZ_COMPONENT(EntityUtilityComponent, "{47205907-A0EA-4FFF-A620-04D20C04A379}");
|
||||
|
||||
AZ::EntityId CreateEditorReadyEntity(const AZStd::string& entityName) override;
|
||||
AzFramework::BehaviorComponentId GetOrAddComponentByTypeName(AZ::EntityId entity, const AZStd::string& typeName) override;
|
||||
bool UpdateComponentForEntity(AZ::EntityId entity, AzFramework::BehaviorComponentId component, const AZStd::string& json) override;
|
||||
AZStd::string GetComponentDefaultJson(const AZStd::string& typeName) override;
|
||||
AZStd::vector<ComponentDetails> FindMatchingComponents(const AZStd::string& searchTerm) override;
|
||||
void ResetEntityContext() override;
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
protected:
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
|
||||
// Our own entity context. This API is intended mostly for use in Asset Builders where there is no editor context
|
||||
// Additionally, an entity context is needed when using the Behavior Entity class
|
||||
AZStd::unique_ptr<AzFramework::EntityContext> m_entityContext;
|
||||
|
||||
// TypeId, TypeName, Vector<BaseClassName>
|
||||
AZStd::vector<AZStd::tuple<AZ::TypeId, AZStd::string, AZStd::vector<AZStd::string>>> m_typeInfo;
|
||||
|
||||
// Keep track of the entities we create so they can be reset
|
||||
AZStd::vector<AZ::EntityId> m_createdEntities;
|
||||
};
|
||||
}; // namespace AzToolsFramework
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <AzCore/Component/EntityId.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
#include <AzFramework/Entity/EntityContextBus.h>
|
||||
|
||||
@@ -224,6 +224,7 @@ namespace AzToolsFramework
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AZ::Data::SerializedAssetTracker* assetTracker = settings.m_metadata.Find<AZ::Data::SerializedAssetTracker>();
|
||||
|
||||
referencedAssets = AZStd::move(assetTracker->GetTrackedAssets());
|
||||
@@ -245,6 +246,30 @@ namespace AzToolsFramework
|
||||
entityIdMapper.SetEntityIdGenerationApproach(InstanceEntityIdMapper::EntityIdGenerationApproach::Random);
|
||||
}
|
||||
|
||||
// some assets may come in from the JSON serialzier with no AssetID, but have an asset hint
|
||||
// this attempts to fix up the assets using the assetHint field
|
||||
auto fixUpInvalidAssets = [](AZ::Data::Asset<AZ::Data::AssetData>& asset)
|
||||
{
|
||||
if (!asset.GetId().IsValid() && !asset.GetHint().empty())
|
||||
{
|
||||
AZ::Data::AssetId assetId;
|
||||
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
|
||||
assetId,
|
||||
&AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
|
||||
asset.GetHint().c_str(),
|
||||
AZ::Data::s_invalidAssetType,
|
||||
false);
|
||||
|
||||
if (assetId.IsValid())
|
||||
{
|
||||
asset.Create(assetId, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto tracker = AZ::Data::SerializedAssetTracker{};
|
||||
tracker.SetAssetFixUp(fixUpInvalidAssets);
|
||||
|
||||
AZ::JsonDeserializerSettings settings;
|
||||
// The InstanceEntityIdMapper is registered twice because it's used in several places during deserialization where one is
|
||||
// specific for the InstanceEntityIdMapper and once for the generic JsonEntityIdMapper. Because the Json Serializer's meta
|
||||
@@ -252,16 +277,17 @@ namespace AzToolsFramework
|
||||
settings.m_metadata.Add(static_cast<AZ::JsonEntityIdSerializer::JsonEntityIdMapper*>(&entityIdMapper));
|
||||
settings.m_metadata.Add(&entityIdMapper);
|
||||
settings.m_metadata.Create<InstanceEntityScrubber>(newlyAddedEntities);
|
||||
settings.m_metadata.Add(tracker);
|
||||
|
||||
AZStd::string scratchBuffer;
|
||||
auto issueReportingCallback = [&scratchBuffer](
|
||||
AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result,
|
||||
AZStd::string_view path) -> AZ::JsonSerializationResult::ResultCode
|
||||
AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result,
|
||||
AZStd::string_view path) -> AZ::JsonSerializationResult::ResultCode
|
||||
{
|
||||
return Internal::JsonIssueReporter(scratchBuffer, message, result, path);
|
||||
};
|
||||
settings.m_reporting = AZStd::move(issueReportingCallback);
|
||||
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(instance, prefabDom, settings);
|
||||
|
||||
AZ::Data::AssetManager::Instance().ResumeAssetRelease();
|
||||
|
||||
@@ -50,17 +50,19 @@ namespace AzToolsFramework
|
||||
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
AZ_Assert(settingsRegistry, "Settings registry is not set");
|
||||
|
||||
|
||||
[[maybe_unused]] bool result =
|
||||
settingsRegistry->Get(m_projectPathWithOsSeparator.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
|
||||
AZ_Warning("Prefab", result, "Couldn't retrieve project root path");
|
||||
m_projectPathWithSlashSeparator = AZ::IO::Path(m_projectPathWithOsSeparator.Native(), '/').MakePreferred();
|
||||
|
||||
AZ::Interface<PrefabLoaderInterface>::Register(this);
|
||||
m_scriptingPrefabLoader.Connect(this);
|
||||
}
|
||||
|
||||
void PrefabLoader::UnregisterPrefabLoaderInterface()
|
||||
{
|
||||
m_scriptingPrefabLoader.Disconnect();
|
||||
AZ::Interface<PrefabLoaderInterface>::Unregister(this);
|
||||
}
|
||||
|
||||
@@ -568,7 +570,7 @@ namespace AzToolsFramework
|
||||
(pathStr.find_first_of(AZ_FILESYSTEM_INVALID_CHARACTERS) == AZStd::string::npos) &&
|
||||
(pathStr.back() != '\\' && pathStr.back() != '/');
|
||||
}
|
||||
|
||||
|
||||
AZ::IO::Path PrefabLoader::GetFullPath(AZ::IO::PathView path)
|
||||
{
|
||||
AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path).MakePreferred();
|
||||
@@ -596,26 +598,38 @@ namespace AzToolsFramework
|
||||
{
|
||||
// The asset system provided us with a valid root folder and relative path, so return it.
|
||||
fullPath = AZ::IO::Path(rootFolder) / assetInfo.m_relativePath;
|
||||
return fullPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If for some reason the Asset system couldn't provide a relative path, provide some fallback logic.
|
||||
|
||||
// Check to see if the AssetProcessor is ready. If it *is* and we didn't get a path, print an error then follow
|
||||
// the fallback logic. If it's *not* ready, we're probably either extremely early in a tool startup flow or inside
|
||||
// a unit test, so just execute the fallback logic without an error.
|
||||
[[maybe_unused]] bool assetProcessorReady = false;
|
||||
AzFramework::AssetSystemRequestBus::BroadcastResult(
|
||||
assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady);
|
||||
|
||||
AZ_Error(
|
||||
"Prefab", !assetProcessorReady, "Full source path for '%.*s' could not be determined. Using fallback logic.",
|
||||
AZ_STRING_ARG(path.Native()));
|
||||
|
||||
// If a relative path was passed in, make it relative to the project root.
|
||||
fullPath = AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator);
|
||||
// attempt to find the absolute from the Cache folder
|
||||
AZStd::string assetRootFolder;
|
||||
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->Get(assetRootFolder, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
|
||||
}
|
||||
fullPath = AZ::IO::Path(assetRootFolder) / path;
|
||||
if (fullPath.IsAbsolute() && AZ::IO::SystemFile::Exists(fullPath.c_str()))
|
||||
{
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
// If for some reason the Asset system couldn't provide a relative path, provide some fallback logic.
|
||||
|
||||
// Check to see if the AssetProcessor is ready. If it *is* and we didn't get a path, print an error then follow
|
||||
// the fallback logic. If it's *not* ready, we're probably either extremely early in a tool startup flow or inside
|
||||
// a unit test, so just execute the fallback logic without an error.
|
||||
[[maybe_unused]] bool assetProcessorReady = false;
|
||||
AzFramework::AssetSystemRequestBus::BroadcastResult(
|
||||
assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady);
|
||||
|
||||
AZ_Error(
|
||||
"Prefab", !assetProcessorReady, "Full source path for '%.*s' could not be determined. Using fallback logic.",
|
||||
AZ_STRING_ARG(path.Native()));
|
||||
|
||||
// If a relative path was passed in, make it relative to the project root.
|
||||
fullPath = AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator);
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
|
||||
#include <Prefab/ScriptingPrefabLoader.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -114,6 +115,7 @@ namespace AzToolsFramework
|
||||
void SetSaveAllPrefabsPreference(SaveAllPrefabsPreference saveAllPrefabsPreference) override;
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Copies the template dom provided and manipulates it into the proper format to be saved to disk.
|
||||
* @param templateRef The template whose dom we want to transform into the proper format to be saved to disk.
|
||||
@@ -177,6 +179,7 @@ namespace AzToolsFramework
|
||||
AZStd::optional<AZStd::pair<PrefabDom, AZ::IO::Path>> StoreTemplateIntoFileFormat(TemplateId templateId);
|
||||
|
||||
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
|
||||
ScriptingPrefabLoader m_scriptingPrefabLoader;
|
||||
AZ::IO::Path m_projectPathWithOsSeparator;
|
||||
AZ::IO::Path m_projectPathWithSlashSeparator;
|
||||
};
|
||||
|
||||
@@ -99,7 +99,6 @@ namespace AzToolsFramework
|
||||
// Generates a new path
|
||||
static AZ::IO::Path GeneratePath();
|
||||
};
|
||||
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace Prefab
|
||||
{
|
||||
// Ebus for script-friendly APIs for the prefab loader
|
||||
struct PrefabLoaderScriptingTraits : AZ::EBusTraits
|
||||
{
|
||||
AZ_TYPE_INFO(PrefabLoaderScriptingTraits, "{C344B7D8-8299-48C9-8450-26E1332EA011}");
|
||||
|
||||
virtual ~PrefabLoaderScriptingTraits() = default;
|
||||
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
|
||||
/**
|
||||
* Saves a Prefab Template into the provided output string.
|
||||
* Converts Prefab Template form into .prefab form by collapsing nested Template info
|
||||
* into a source path and patches.
|
||||
* @param templateId Id of the template to be saved
|
||||
* @return Will contain the serialized template json on success
|
||||
*/
|
||||
virtual AZ::Outcome<AZStd::string, void> SaveTemplateToString(TemplateId templateId) = 0;
|
||||
};
|
||||
|
||||
using PrefabLoaderScriptingBus = AZ::EBus<PrefabLoaderScriptingTraits>;
|
||||
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
|
||||
@@ -36,12 +37,14 @@ namespace AzToolsFramework
|
||||
m_instanceToTemplatePropagator.RegisterInstanceToTemplateInterface();
|
||||
m_prefabPublicHandler.RegisterPrefabPublicHandlerInterface();
|
||||
m_prefabPublicRequestHandler.Connect();
|
||||
m_prefabSystemScriptingHandler.Connect(this);
|
||||
AZ::SystemTickBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::Deactivate()
|
||||
{
|
||||
AZ::SystemTickBus::Handler::BusDisconnect();
|
||||
m_prefabSystemScriptingHandler.Disconnect();
|
||||
m_prefabPublicRequestHandler.Disconnect();
|
||||
m_prefabPublicHandler.UnregisterPrefabPublicHandlerInterface();
|
||||
m_instanceToTemplatePropagator.UnregisterInstanceToTemplateInterface();
|
||||
@@ -58,13 +61,24 @@ namespace AzToolsFramework
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover::Reflect(context);
|
||||
PrefabPublicRequestHandler::Reflect(context);
|
||||
PrefabLoader::Reflect(context);
|
||||
PrefabSystemScriptingHandler::Reflect(context);
|
||||
|
||||
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serialize->Class<PrefabSystemComponent, AZ::Component>()->Version(1);
|
||||
}
|
||||
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
|
||||
behaviorContext->EBus<PrefabLoaderScriptingBus>("PrefabLoaderScriptingBus")
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "prefab")
|
||||
->Attribute(AZ::Script::Attributes::Category, "Prefab")
|
||||
->Event("SaveTemplateToString", &PrefabLoaderScriptingBus::Events::SaveTemplateToString);
|
||||
;
|
||||
}
|
||||
|
||||
AZ::JsonRegistrationContext* jsonRegistration = azrtti_cast<AZ::JsonRegistrationContext*>(context);
|
||||
if (jsonRegistration)
|
||||
{
|
||||
@@ -145,7 +159,7 @@ namespace AzToolsFramework
|
||||
newInstance->SetTemplateId(newTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude)
|
||||
{
|
||||
UpdatePrefabInstances(templateId, immediate, instanceToExclude);
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicRequestHandler.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Template/Template.h>
|
||||
#include <Prefab/PrefabSystemScriptingHandler.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -219,7 +220,7 @@ namespace AzToolsFramework
|
||||
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume,
|
||||
AZ::IO::PathView filePath, AZStd::unique_ptr<AZ::Entity> containerEntity = nullptr,
|
||||
InstanceOptionalReference parent = AZStd::nullopt, bool shouldCreateLinks = true) override;
|
||||
|
||||
|
||||
PrefabDom& FindTemplateDom(TemplateId templateId) override;
|
||||
|
||||
/**
|
||||
@@ -244,7 +245,7 @@ namespace AzToolsFramework
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PrefabSystemComponent);
|
||||
|
||||
|
||||
/**
|
||||
* Builds a new Prefab Template out of entities and instances and returns the first instance comprised of
|
||||
* these entities and instances.
|
||||
@@ -412,6 +413,8 @@ namespace AzToolsFramework
|
||||
|
||||
// Handler of the public Prefab requests.
|
||||
PrefabPublicRequestHandler m_prefabPublicRequestHandler;
|
||||
|
||||
PrefabSystemScriptingHandler m_prefabSystemScriptingHandler;
|
||||
};
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+1
-2
@@ -78,8 +78,7 @@ namespace AzToolsFramework
|
||||
AZStd::unique_ptr<AZ::Entity> containerEntity = nullptr, InstanceOptionalReference parent = AZStd::nullopt,
|
||||
bool shouldCreateLinks = true) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/Instance.h>
|
||||
#include <AzToolsFramework/Prefab/Link/Link.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
|
||||
#include <AzToolsFramework/Prefab/Template/Template.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace Prefab
|
||||
{
|
||||
// Bus that exposes a script-friendly interface to the PrefabSystemComponent
|
||||
struct PrefabSystemScriptingEbusTraits : AZ::EBusTraits
|
||||
{
|
||||
using MutexType = AZ::NullMutex;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
|
||||
virtual TemplateId CreatePrefabTemplate(
|
||||
const AZStd::vector<AZ::EntityId>& entityIds, const AZStd::string& filePath) = 0;
|
||||
};
|
||||
|
||||
using PrefabSystemScriptingBus = AZ::EBus<PrefabSystemScriptingEbusTraits>;
|
||||
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Component/ComponentApplicationBus.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <Prefab/PrefabSystemScriptingHandler.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
|
||||
namespace AzToolsFramework::Prefab
|
||||
{
|
||||
void PrefabSystemScriptingHandler::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->ConstantProperty("InvalidTemplateId", BehaviorConstant(InvalidTemplateId))
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "prefab")
|
||||
->Attribute(AZ::Script::Attributes::Category, "Prefab");
|
||||
|
||||
behaviorContext->EBus<PrefabSystemScriptingBus>("PrefabSystemScriptingBus")
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "prefab")
|
||||
->Attribute(AZ::Script::Attributes::Category, "Prefab")
|
||||
->Event("CreatePrefab", &PrefabSystemScriptingBus::Events::CreatePrefabTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabSystemScriptingHandler::Connect(PrefabSystemComponentInterface* prefabSystemComponentInterface)
|
||||
{
|
||||
AZ_Assert(prefabSystemComponentInterface != nullptr, "prefabSystemComponentInterface must not be null");
|
||||
m_prefabSystemComponentInterface = prefabSystemComponentInterface;
|
||||
PrefabSystemScriptingBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void PrefabSystemScriptingHandler::Disconnect()
|
||||
{
|
||||
PrefabSystemScriptingBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
TemplateId PrefabSystemScriptingHandler::CreatePrefabTemplate(const AZStd::vector<AZ::EntityId>& entityIds, const AZStd::string& filePath)
|
||||
{
|
||||
AZStd::vector<AZ::Entity*> entities;
|
||||
|
||||
for (const auto& entityId : entityIds)
|
||||
{
|
||||
AZ::Entity* entity = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
|
||||
|
||||
AZ_Warning(
|
||||
"PrefabSystemComponent", entity, "EntityId %s was not found and will not be added to the prefab",
|
||||
entityId.ToString().c_str());
|
||||
|
||||
if (entity)
|
||||
{
|
||||
entities.push_back(entity);
|
||||
}
|
||||
}
|
||||
|
||||
auto prefab = m_prefabSystemComponentInterface->CreatePrefab(entities, {}, AZ::IO::PathView(AZStd::string_view(filePath)));
|
||||
|
||||
if (!prefab)
|
||||
{
|
||||
AZ_Error("PrefabSystemComponenent", false, "Failed to create prefab %s", filePath.c_str());
|
||||
return InvalidTemplateId;
|
||||
}
|
||||
|
||||
return prefab->GetTemplateId();
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <Prefab/PrefabSystemScriptingBus.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace Prefab
|
||||
{
|
||||
class PrefabSystemScriptingHandler
|
||||
: PrefabSystemScriptingBus::Handler
|
||||
{
|
||||
public:
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
PrefabSystemScriptingHandler() = default;
|
||||
|
||||
void Connect(PrefabSystemComponentInterface* prefabSystemComponentInterface);
|
||||
void Disconnect();
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY(PrefabSystemScriptingHandler);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// PrefabSystemScriptingBus implementation
|
||||
TemplateId CreatePrefabTemplate(const AZStd::vector<AZ::EntityId>& entityIds, const AZStd::string& filePath) override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
|
||||
};
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#include <Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
#include <AzFramework/FileFunc/FileFunc.h>
|
||||
|
||||
namespace AZ::Prefab
|
||||
{
|
||||
static constexpr const char s_useProceduralPrefabsKey[] = "/O3DE/Preferences/Prefabs/UseProceduralPrefabs";
|
||||
|
||||
// ProceduralPrefabAsset
|
||||
|
||||
ProceduralPrefabAsset::ProceduralPrefabAsset(const AZ::Data::AssetId& assetId)
|
||||
: AZ::Data::AssetData(assetId)
|
||||
, m_templateId(AzToolsFramework::Prefab::InvalidTemplateId)
|
||||
{
|
||||
}
|
||||
|
||||
void ProceduralPrefabAsset::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
PrefabDomData::Reflect(context);
|
||||
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
|
||||
{
|
||||
serializeContext->Class<ProceduralPrefabAsset, AZ::Data::AssetData>()
|
||||
->Version(1)
|
||||
->Field("Template Name", &ProceduralPrefabAsset::m_templateName)
|
||||
->Field("Template ID", &ProceduralPrefabAsset::m_templateId);
|
||||
}
|
||||
}
|
||||
|
||||
const AZStd::string& ProceduralPrefabAsset::GetTemplateName() const
|
||||
{
|
||||
return m_templateName;
|
||||
}
|
||||
|
||||
void ProceduralPrefabAsset::SetTemplateName(AZStd::string templateName)
|
||||
{
|
||||
m_templateName = AZStd::move(templateName);
|
||||
}
|
||||
|
||||
AzToolsFramework::Prefab::TemplateId ProceduralPrefabAsset::GetTemplateId() const
|
||||
{
|
||||
return m_templateId;
|
||||
}
|
||||
|
||||
void ProceduralPrefabAsset::SetTemplateId(AzToolsFramework::Prefab::TemplateId templateId)
|
||||
{
|
||||
m_templateId = templateId;
|
||||
}
|
||||
|
||||
bool ProceduralPrefabAsset::UseProceduralPrefabs()
|
||||
{
|
||||
bool useProceduralPrefabs = false;
|
||||
bool result = AZ::SettingsRegistry::Get()->GetObject(useProceduralPrefabs, s_useProceduralPrefabsKey);
|
||||
return result && useProceduralPrefabs;
|
||||
}
|
||||
|
||||
// PrefabDomData
|
||||
|
||||
void PrefabDomData::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto* jsonContext = azrtti_cast<AZ::JsonRegistrationContext*>(context))
|
||||
{
|
||||
jsonContext->Serializer<PrefabDomDataJsonSerializer>()->HandlesType<PrefabDomData>();
|
||||
}
|
||||
|
||||
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<PrefabDomData>()
|
||||
->Version(1);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabDomData::CopyValue(const rapidjson::Value& inputValue)
|
||||
{
|
||||
m_prefabDom.CopyFrom(inputValue, m_prefabDom.GetAllocator());
|
||||
}
|
||||
|
||||
const AzToolsFramework::Prefab::PrefabDom& PrefabDomData::GetValue() const
|
||||
{
|
||||
return m_prefabDom;
|
||||
}
|
||||
|
||||
// PrefabDomDataJsonSerializer
|
||||
|
||||
AZ::JsonSerializationResult::Result PrefabDomDataJsonSerializer::Load(
|
||||
void* outputValue,
|
||||
[[maybe_unused]] const AZ::Uuid& outputValueTypeId,
|
||||
const rapidjson::Value& inputValue,
|
||||
AZ::JsonDeserializerContext& context)
|
||||
{
|
||||
AZ_Assert(outputValueTypeId == azrtti_typeid<PrefabDomData>(),
|
||||
"PrefabDomDataJsonSerializer Load against output typeID that was not PrefabDomData");
|
||||
AZ_Assert(outputValue, "PrefabDomDataJsonSerializer Load against null output");
|
||||
|
||||
namespace JSR = AZ::JsonSerializationResult;
|
||||
JSR::ResultCode result(JSR::Tasks::ReadField);
|
||||
|
||||
if (inputValue.IsObject() == false)
|
||||
{
|
||||
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Missing, "Missing object"));
|
||||
return context.Report(result, "Prefab should be an object.");
|
||||
}
|
||||
|
||||
if (inputValue.MemberCount() < 1)
|
||||
{
|
||||
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Missing, "Missing members"));
|
||||
return context.Report(result, "Prefab should have multiple members.");
|
||||
}
|
||||
|
||||
auto* outputVariable = reinterpret_cast<PrefabDomData*>(outputValue);
|
||||
outputVariable->CopyValue(inputValue);
|
||||
return context.Report(result, "Loaded procedural prefab");
|
||||
}
|
||||
|
||||
AZ::JsonSerializationResult::Result PrefabDomDataJsonSerializer::Store(
|
||||
rapidjson::Value& outputValue,
|
||||
const void* inputValue,
|
||||
[[maybe_unused]] const void* defaultValue,
|
||||
[[maybe_unused]] const AZ::Uuid& valueTypeId,
|
||||
AZ::JsonSerializerContext& context)
|
||||
{
|
||||
AZ_Assert(inputValue, "Input value for PrefabDomDataJsonSerializer can't be null.");
|
||||
AZ_Assert(azrtti_typeid<PrefabDomData>() == valueTypeId,
|
||||
"Unable to Serialize because the provided type is not PrefabGroup::PrefabDomData.");
|
||||
|
||||
const PrefabDomData* prefabDomData = reinterpret_cast<const PrefabDomData*>(inputValue);
|
||||
|
||||
namespace JSR = AZ::JsonSerializationResult;
|
||||
JSR::ResultCode result(JSR::Tasks::WriteValue);
|
||||
outputValue.SetObject();
|
||||
outputValue.CopyFrom(prefabDomData->GetValue(), context.GetJsonAllocator());
|
||||
return context.Report(result, "Stored procedural prefab");
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
|
||||
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
|
||||
|
||||
namespace AZ::Prefab
|
||||
{
|
||||
//! A wrapper around the JSON DOM type so that the assets can read in and write out
|
||||
//! JSON directly since Prefabs are JSON serialized entity-component data
|
||||
class PrefabDomData final
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(PrefabDomData, "{C73A3360-D772-4D41-9118-A039BF9340C1}");
|
||||
AZ_CLASS_ALLOCATOR(PrefabDomData, AZ::SystemAllocator, 0);
|
||||
|
||||
PrefabDomData() = default;
|
||||
~PrefabDomData() = default;
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
void CopyValue(const rapidjson::Value& inputValue);
|
||||
const AzToolsFramework::Prefab::PrefabDom& GetValue() const;
|
||||
|
||||
private:
|
||||
AzToolsFramework::Prefab::PrefabDom m_prefabDom;
|
||||
};
|
||||
|
||||
//! Registered to help read/write JSON for the PrefabDomData::m_prefabDom
|
||||
class PrefabDomDataJsonSerializer final
|
||||
: public AZ::BaseJsonSerializer
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(PrefabDomDataJsonSerializer, "{9FC48652-A00B-4EFA-8FD9-345A8E625439}", BaseJsonSerializer);
|
||||
AZ_CLASS_ALLOCATOR(PrefabDomDataJsonSerializer, AZ::SystemAllocator, 0);
|
||||
|
||||
~PrefabDomDataJsonSerializer() override = default;
|
||||
|
||||
AZ::JsonSerializationResult::Result Load(
|
||||
void* outputValue,
|
||||
const AZ::Uuid& outputValueTypeId,
|
||||
const rapidjson::Value& inputValue,
|
||||
AZ::JsonDeserializerContext& context) override;
|
||||
|
||||
AZ::JsonSerializationResult::Result Store(
|
||||
rapidjson::Value& outputValue,
|
||||
const void* inputValue,
|
||||
const void* defaultValue,
|
||||
const AZ::Uuid& valueTypeId,
|
||||
AZ::JsonSerializerContext& context) override;
|
||||
};
|
||||
|
||||
//! An asset type to register templates into the Prefab system so that they
|
||||
//! can instantiate like Authored Prefabs
|
||||
class ProceduralPrefabAsset
|
||||
: public AZ::Data::AssetData
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(ProceduralPrefabAsset, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(ProceduralPrefabAsset, "{9B7C8459-471E-4EAD-A363-7990CC4065A9}", AZ::Data::AssetData);
|
||||
|
||||
static bool UseProceduralPrefabs();
|
||||
|
||||
ProceduralPrefabAsset(const AZ::Data::AssetId& assetId = AZ::Data::AssetId());
|
||||
~ProceduralPrefabAsset() override = default;
|
||||
ProceduralPrefabAsset(const ProceduralPrefabAsset& rhs) = delete;
|
||||
ProceduralPrefabAsset& operator=(const ProceduralPrefabAsset& rhs) = delete;
|
||||
|
||||
const AZStd::string& GetTemplateName() const;
|
||||
void SetTemplateName(AZStd::string templateName);
|
||||
|
||||
AzToolsFramework::Prefab::TemplateId GetTemplateId() const;
|
||||
void SetTemplateId(AzToolsFramework::Prefab::TemplateId templateId);
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
private:
|
||||
AZStd::string m_templateName;
|
||||
AzToolsFramework::Prefab::TemplateId m_templateId;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Prefab/ScriptingPrefabLoader.h>
|
||||
|
||||
namespace AzToolsFramework::Prefab
|
||||
{
|
||||
void ScriptingPrefabLoader::Connect(PrefabLoaderInterface* prefabLoaderInterface)
|
||||
{
|
||||
AZ_Assert(prefabLoaderInterface, "prefabLoaderInterface must not be null");
|
||||
|
||||
m_prefabLoaderInterface = prefabLoaderInterface;
|
||||
PrefabLoaderScriptingBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void ScriptingPrefabLoader::Disconnect()
|
||||
{
|
||||
PrefabLoaderScriptingBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
AZ::Outcome<AZStd::string, void> ScriptingPrefabLoader::SaveTemplateToString(TemplateId templateId)
|
||||
{
|
||||
AZStd::string json;
|
||||
|
||||
if (m_prefabLoaderInterface->SaveTemplateToString(templateId, json))
|
||||
{
|
||||
return AZ::Success(json);
|
||||
}
|
||||
|
||||
return AZ::Failure();
|
||||
}
|
||||
} // namespace AzToolsFramework::Prefab
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Prefab/PrefabLoaderInterface.h>
|
||||
#include <Prefab/PrefabLoaderScriptingBus.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace Prefab
|
||||
{
|
||||
/**
|
||||
* The Scripting Prefab Loader handles scripting-friendly API requests for the prefab loader
|
||||
*/
|
||||
class ScriptingPrefabLoader
|
||||
: private PrefabLoaderScriptingBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(ScriptingPrefabLoader, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(ScriptingPrefabLoader, "{ABC3C989-4D4F-41E7-B25B-B0FEF97177E6}");
|
||||
|
||||
void Connect(PrefabLoaderInterface* prefabLoaderInterface);
|
||||
void Disconnect();
|
||||
|
||||
private:
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// PrefabLoaderRequestBus implementation
|
||||
AZ::Outcome<AZStd::string, void> SaveTemplateToString(TemplateId templateId) override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PrefabLoaderInterface* m_prefabLoaderInterface = nullptr;
|
||||
};
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+1
-1
@@ -1025,7 +1025,7 @@ namespace AzToolsFramework
|
||||
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/LuaScript.svg")
|
||||
->Attribute(AZ::Edit::Attributes::PrimaryAssetType, AZ::AzTypeInfo<AZ::ScriptAsset>::Uuid())
|
||||
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Script.png")
|
||||
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/lua-script/")
|
||||
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/scripting/lua-script/")
|
||||
->DataElement("AssetRef", &ScriptEditorComponent::m_scriptAsset, "Script", "Which script to use")
|
||||
->Attribute(AZ::Edit::Attributes::ChangeNotify, &ScriptEditorComponent::ScriptHasChanged)
|
||||
->Attribute("BrowseIcon", ":/stylesheet/img/UI20/browse-edit-select-files.svg")
|
||||
|
||||
+73
@@ -13,6 +13,7 @@
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
|
||||
#include <AzFramework/API/ApplicationAPI.h>
|
||||
#include <AzFramework/Asset/AssetSystemBus.h>
|
||||
@@ -25,6 +26,9 @@
|
||||
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
|
||||
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h>
|
||||
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
|
||||
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
|
||||
@@ -218,6 +222,16 @@ namespace AzToolsFramework
|
||||
instantiateAction, &QAction::triggered, instantiateAction, [] { ContextMenu_InstantiatePrefab(); });
|
||||
}
|
||||
|
||||
// Instantiate Procedural Prefab
|
||||
if (AZ::Prefab::ProceduralPrefabAsset::UseProceduralPrefabs())
|
||||
{
|
||||
QAction* action = menu->addAction(QObject::tr("Instantiate Procedural Prefab..."));
|
||||
action->setToolTip(QObject::tr("Instantiates a procedural prefab file in a prefab."));
|
||||
|
||||
QObject::connect(
|
||||
action, &QAction::triggered, action, [] { ContextMenu_InstantiateProceduralPrefab(); });
|
||||
}
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
bool itemWasShown = false;
|
||||
@@ -435,6 +449,38 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::ContextMenu_InstantiateProceduralPrefab()
|
||||
{
|
||||
AZStd::string prefabAssetPath;
|
||||
bool hasUserForProceduralPrefabAsset = QueryUserForProceduralPrefabAsset(prefabAssetPath);
|
||||
|
||||
if (hasUserForProceduralPrefabAsset)
|
||||
{
|
||||
AZ::EntityId parentId;
|
||||
AZ::Vector3 position = AZ::Vector3::CreateZero();
|
||||
|
||||
EntityIdList selectedEntities;
|
||||
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
|
||||
if (selectedEntities.size() == 1)
|
||||
{
|
||||
parentId = selectedEntities.front();
|
||||
AZ::TransformBus::EventResult(position, parentId, &AZ::TransformInterface::GetWorldTranslation);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise return since it needs to be inside an authored prefab
|
||||
return;
|
||||
}
|
||||
|
||||
// Instantiating from context menu always puts the instance at the root level
|
||||
auto createPrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(prefabAssetPath, parentId, position);
|
||||
if (!createPrefabOutcome.IsSuccess())
|
||||
{
|
||||
WarnUserOfError("Prefab Instantiation Error", createPrefabOutcome.GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity)
|
||||
{
|
||||
s_prefabFocusInterface->FocusOnOwningPrefab(containerEntity);
|
||||
@@ -690,6 +736,33 @@ namespace AzToolsFramework
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PrefabIntegrationManager::QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath)
|
||||
{
|
||||
using namespace AzToolsFramework;
|
||||
auto selection = AssetBrowser::AssetSelectionModel::AssetTypeSelection(azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>());
|
||||
EditorRequests::Bus::Broadcast(&AzToolsFramework::EditorRequests::BrowseForAssets, selection);
|
||||
|
||||
if (!selection.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto product = azrtti_cast<const ProductAssetBrowserEntry*>(selection.GetResult());
|
||||
if (product == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
outPrefabAssetPath = product->GetRelativePath();
|
||||
|
||||
auto asset = AZ::Data::AssetManager::Instance().GetAsset(
|
||||
product->GetAssetId(),
|
||||
azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>(),
|
||||
AZ::Data::AssetLoadBehavior::Default);
|
||||
|
||||
return asset.BlockUntilLoadComplete() != AZ::Data::AssetData::AssetStatus::Error;
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::WarnUserOfError(AZStd::string_view title, AZStd::string_view message)
|
||||
{
|
||||
QWidget* activeWindow = QApplication::activeWindow();
|
||||
|
||||
@@ -91,6 +91,7 @@ namespace AzToolsFramework
|
||||
// Context menu item handlers
|
||||
static void ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities);
|
||||
static void ContextMenu_InstantiatePrefab();
|
||||
static void ContextMenu_InstantiateProceduralPrefab();
|
||||
static void ContextMenu_EditPrefab(AZ::EntityId containerEntity);
|
||||
static void ContextMenu_SavePrefab(AZ::EntityId containerEntity);
|
||||
static void ContextMenu_DeleteSelected();
|
||||
@@ -101,6 +102,7 @@ namespace AzToolsFramework
|
||||
const AZStd::string& suggestedName, const char* initialTargetDirectory, AZ::u32 prefabUserSettingsId, QWidget* activeWindow,
|
||||
AZStd::string& outPrefabName, AZStd::string& outPrefabFilePath);
|
||||
static bool QueryUserForPrefabFilePath(AZStd::string& outPrefabFilePath);
|
||||
static bool QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath);
|
||||
static void WarnUserOfError(AZStd::string_view title, AZStd::string_view message);
|
||||
|
||||
// Path and filename generation
|
||||
|
||||
+2
-42
@@ -103,10 +103,6 @@ namespace AzToolsFramework
|
||||
static const char* const ResetEntityTransformDesc = "Reset transform based on manipulator mode";
|
||||
static const char* const ResetManipulatorTitle = "Reset Manipulator";
|
||||
static const char* const ResetManipulatorDesc = "Reset the manipulator to recenter it on the selected entity";
|
||||
static const char* const ResetTransformLocalTitle = "Reset Transform (Local)";
|
||||
static const char* const ResetTransformLocalDesc = "Reset transform to local space";
|
||||
static const char* const ResetTransformWorldTitle = "Reset Transform (World)";
|
||||
static const char* const ResetTransformWorldDesc = "Reset transform to world space";
|
||||
|
||||
static const char* const EntityBoxSelectUndoRedoDesc = "Box Select Entities";
|
||||
static const char* const EntityDeselectUndoRedoDesc = "Deselect Entity";
|
||||
@@ -2424,45 +2420,9 @@ namespace AzToolsFramework
|
||||
|
||||
AddAction(
|
||||
m_actions, { QKeySequence(Qt::CTRL + Qt::Key_R) }, EditResetManipulator, ResetManipulatorTitle, ResetManipulatorDesc,
|
||||
AZStd::bind(AZStd::mem_fn(&EditorTransformComponentSelection::DelegateClearManipulatorOverride), this));
|
||||
|
||||
AddAction(
|
||||
m_actions, { QKeySequence(Qt::ALT + Qt::Key_R) }, EditResetLocal, ResetTransformLocalTitle, ResetTransformLocalDesc,
|
||||
[this]()
|
||||
[this]
|
||||
{
|
||||
switch (m_mode)
|
||||
{
|
||||
case Mode::Rotation:
|
||||
ResetOrientationForSelectedEntitiesLocal();
|
||||
break;
|
||||
case Mode::Scale:
|
||||
CopyScaleToSelectedEntitiesIndividualWorld(1.0f);
|
||||
break;
|
||||
case Mode::Translation:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
AddAction(
|
||||
m_actions, { QKeySequence(Qt::SHIFT + Qt::Key_R) }, EditResetWorld, ResetTransformWorldTitle, ResetTransformWorldDesc,
|
||||
[this]()
|
||||
{
|
||||
switch (m_mode)
|
||||
{
|
||||
case Mode::Rotation:
|
||||
{
|
||||
// begin an undo batch so operations inside CopyOrientation... and
|
||||
// DelegateClear... are grouped into a single undo/redo
|
||||
ScopedUndoBatch undoBatch{ ResetTransformWorldTitle };
|
||||
CopyOrientationToSelectedEntitiesIndividual(AZ::Quaternion::CreateIdentity());
|
||||
ClearManipulatorOrientationOverride();
|
||||
}
|
||||
break;
|
||||
case Mode::Scale:
|
||||
case Mode::Translation:
|
||||
break;
|
||||
}
|
||||
DelegateClearManipulatorOverride();
|
||||
});
|
||||
|
||||
AddAction(
|
||||
|
||||
-2
@@ -31,8 +31,6 @@ namespace AzToolsFramework
|
||||
constexpr inline AZ::Crc32 EditPivot = AZ_CRC_CE("com.o3de.action.editortransform.editpivot");
|
||||
constexpr inline AZ::Crc32 EditReset = AZ_CRC_CE("com.o3de.action.editortransform.editreset");
|
||||
constexpr inline AZ::Crc32 EditResetManipulator = AZ_CRC_CE("com.o3de.action.editortransform.editresetmanipulator");
|
||||
constexpr inline AZ::Crc32 EditResetLocal = AZ_CRC_CE("com.o3de.action.editortransform.editresetlocal");
|
||||
constexpr inline AZ::Crc32 EditResetWorld = AZ_CRC_CE("com.o3de.action.editortransform.editresetworld");
|
||||
constexpr inline AZ::Crc32 ViewportUiVisible = AZ_CRC_CE("com.o3de.action.editortransform.viewportuivisible");
|
||||
//@}
|
||||
|
||||
|
||||
@@ -154,6 +154,8 @@ set(FILES
|
||||
Entity/SliceEditorEntityOwnershipService.h
|
||||
Entity/SliceEditorEntityOwnershipService.cpp
|
||||
Entity/SliceEditorEntityOwnershipServiceBus.h
|
||||
Entity/EntityUtilityComponent.h
|
||||
Entity/EntityUtilityComponent.cpp
|
||||
Fingerprinting/TypeFingerprinter.h
|
||||
Fingerprinting/TypeFingerprinter.cpp
|
||||
FocusMode/FocusModeInterface.h
|
||||
@@ -648,9 +650,15 @@ set(FILES
|
||||
Prefab/PrefabLoader.h
|
||||
Prefab/PrefabLoader.cpp
|
||||
Prefab/PrefabLoaderInterface.h
|
||||
Prefab/PrefabLoaderScriptingBus.h
|
||||
Prefab/ScriptingPrefabLoader.h
|
||||
Prefab/ScriptingPrefabLoader.cpp
|
||||
Prefab/PrefabSystemComponent.h
|
||||
Prefab/PrefabSystemComponent.cpp
|
||||
Prefab/PrefabSystemComponentInterface.h
|
||||
Prefab/PrefabSystemScriptingBus.h
|
||||
Prefab/PrefabSystemScriptingHandler.h
|
||||
Prefab/PrefabSystemScriptingHandler.cpp
|
||||
Prefab/Instance/Instance.h
|
||||
Prefab/Instance/Instance.cpp
|
||||
Prefab/Instance/InstanceSerializer.h
|
||||
@@ -673,6 +681,8 @@ set(FILES
|
||||
Prefab/Instance/TemplateInstanceMapperInterface.h
|
||||
Prefab/Link/Link.h
|
||||
Prefab/Link/Link.cpp
|
||||
Prefab/Procedural/ProceduralPrefabAsset.h
|
||||
Prefab/Procedural/ProceduralPrefabAsset.cpp
|
||||
Prefab/PrefabPublicHandler.h
|
||||
Prefab/PrefabPublicHandler.cpp
|
||||
Prefab/PrefabPublicInterface.h
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzFramework/Entity/BehaviorEntity.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
#include <AzToolsFramework/Application/ToolsApplication.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
#include <Entity/EntityUtilityComponent.h>
|
||||
#include <ToolsComponents/TransformComponent.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
// Global variables for communicating between Lua test code and C++
|
||||
AZ::EntityId g_globalEntityId = AZ::EntityId{};
|
||||
AZStd::string g_globalString = "";
|
||||
AzFramework::BehaviorComponentId g_globalComponentId = {};
|
||||
AZStd::vector<AzToolsFramework::ComponentDetails> g_globalComponentDetails = {};
|
||||
bool g_globalBool = false;
|
||||
|
||||
class EntityUtilityComponentTests
|
||||
: public ToolsApplicationFixture
|
||||
{
|
||||
void InitProperties()
|
||||
{
|
||||
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
|
||||
|
||||
ASSERT_NE(componentApplicationRequests, nullptr);
|
||||
|
||||
auto behaviorContext = componentApplicationRequests->GetBehaviorContext();
|
||||
|
||||
ASSERT_NE(behaviorContext, nullptr);
|
||||
|
||||
behaviorContext->Property("g_globalEntityId", BehaviorValueProperty(&g_globalEntityId));
|
||||
behaviorContext->Property("g_globalString", BehaviorValueProperty(&g_globalString));
|
||||
behaviorContext->Property("g_globalComponentId", BehaviorValueProperty(&g_globalComponentId));
|
||||
behaviorContext->Property("g_globalBool", BehaviorValueProperty(&g_globalBool));
|
||||
behaviorContext->Property("g_globalComponentDetails", BehaviorValueProperty(&g_globalComponentDetails));
|
||||
|
||||
g_globalEntityId = AZ::EntityId{};
|
||||
g_globalString = AZStd::string{};
|
||||
g_globalComponentId = AzFramework::BehaviorComponentId{};
|
||||
g_globalBool = false;
|
||||
g_globalComponentDetails = AZStd::vector<AzToolsFramework::ComponentDetails>{};
|
||||
}
|
||||
|
||||
void SetUpEditorFixtureImpl() override
|
||||
{
|
||||
InitProperties();
|
||||
}
|
||||
|
||||
void TearDownEditorFixtureImpl() override
|
||||
{
|
||||
g_globalString.set_capacity(0); // Free all memory
|
||||
g_globalComponentDetails.set_capacity(0);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, CreateEntity)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
g_globalEntityId = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
my_entity = Entity(g_globalEntityId)
|
||||
g_globalString = my_entity:GetName()
|
||||
)LUA");
|
||||
|
||||
EXPECT_NE(g_globalEntityId, AZ::EntityId{});
|
||||
EXPECT_STREQ(g_globalString.c_str(), "test");
|
||||
|
||||
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(g_globalEntityId);
|
||||
|
||||
ASSERT_NE(entity, nullptr);
|
||||
|
||||
// Test cleaning up, make sure the entity is destroyed
|
||||
AzToolsFramework::EntityUtilityBus::Broadcast(&AzToolsFramework::EntityUtilityBus::Events::ResetEntityContext);
|
||||
|
||||
entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(g_globalEntityId);
|
||||
|
||||
ASSERT_EQ(entity, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, CreateEntityEmptyName)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
g_globalEntityId = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("")
|
||||
)LUA");
|
||||
|
||||
EXPECT_NE(g_globalEntityId, AZ::EntityId{});
|
||||
|
||||
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(g_globalEntityId);
|
||||
|
||||
ASSERT_NE(entity, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, FindComponent)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0 TransformComponent")
|
||||
)LUA");
|
||||
|
||||
EXPECT_TRUE(g_globalComponentId.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, InvalidComponentName)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
sc.Execute(R"LUA(
|
||||
ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "ThisIsNotAComponent-Error")
|
||||
)LUA");
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(g_globalComponentId.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, InvalidComponentId)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
sc.Execute(R"LUA(
|
||||
ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "{1234-hello-world-this-is-not-an-id}")
|
||||
)LUA");
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // Should get 1 error stating the type id is not valid
|
||||
EXPECT_FALSE(g_globalComponentId.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, CreateComponent)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "ScriptEditorComponent")
|
||||
)LUA");
|
||||
|
||||
EXPECT_TRUE(g_globalComponentId.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, UpdateComponent)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
g_globalEntityId = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
comp_id = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(g_globalEntityId, "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent")
|
||||
json_update = [[
|
||||
{
|
||||
"Transform Data": { "Rotate": [0.0, 0.1, 180.0] }
|
||||
}
|
||||
]]
|
||||
g_globalBool = EntityUtilityBus.Broadcast.UpdateComponentForEntity(g_globalEntityId, comp_id, json_update);
|
||||
)LUA");
|
||||
|
||||
EXPECT_TRUE(g_globalBool);
|
||||
EXPECT_NE(g_globalEntityId, AZ::EntityId(AZ::EntityId::InvalidEntityId));
|
||||
|
||||
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(g_globalEntityId);
|
||||
|
||||
auto* transformComponent = entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
|
||||
|
||||
ASSERT_NE(transformComponent, nullptr);
|
||||
|
||||
AZ::Vector3 localRotation = transformComponent->GetLocalRotationQuaternion().GetEulerDegrees();
|
||||
|
||||
EXPECT_EQ(localRotation, AZ::Vector3(.0f, 0.1f, 180.0f));
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, GetComponentJson)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
g_globalString = EntityUtilityBus.Broadcast.GetComponentDefaultJson("ScriptEditorComponent")
|
||||
)LUA");
|
||||
|
||||
EXPECT_STRNE(g_globalString.c_str(), "");
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, GetComponentJsonDoesNotExist)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
sc.Execute(R"LUA(
|
||||
g_globalString = EntityUtilityBus.Broadcast.GetComponentDefaultJson("404")
|
||||
)LUA");
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // 1 error: Failed to find component id for type name 404
|
||||
|
||||
EXPECT_STREQ(g_globalString.c_str(), "");
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, SearchComponents)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
g_globalComponentDetails = EntityUtilityBus.Broadcast.FindMatchingComponents("Transform*")
|
||||
)LUA");
|
||||
|
||||
// There should be 2 transform components
|
||||
EXPECT_EQ(g_globalComponentDetails.size(), 2);
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, SearchComponentsNotFound)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
g_globalComponentDetails = EntityUtilityBus.Broadcast.FindMatchingComponents("404")
|
||||
)LUA");
|
||||
|
||||
EXPECT_EQ(g_globalComponentDetails.size(), 0);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ namespace UnitTest
|
||||
R"X(Executing RC.EXE: '"E:\lyengine\dev\windows\bin\profile\rc.exe" "E:/Directory/File.tga")X",
|
||||
R"X(Executing RC.EXE with working directory : '')X",
|
||||
R"X(ResourceCompiler 64 - bit DEBUG)X",
|
||||
R"X(Platform support : PC, PowerVR, etc2Comp)X",
|
||||
R"X(Platform support : PC, PowerVR)X",
|
||||
R"X(Version 1.1.8.6 Nov 5 2018 13 : 28 : 28)X"
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
||||
#include <Entity/EntityUtilityComponent.h>
|
||||
|
||||
#include <Prefab/PrefabTestComponent.h>
|
||||
#include <Prefab/PrefabTestDomUtils.h>
|
||||
#include <Prefab/PrefabTestFixture.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
TemplateId g_globalTemplateId = {};
|
||||
AZStd::string g_globalPrefabString = "";
|
||||
|
||||
class PrefabScriptingTest : public PrefabTestFixture
|
||||
{
|
||||
void InitProperties() const
|
||||
{
|
||||
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
|
||||
|
||||
ASSERT_NE(componentApplicationRequests, nullptr);
|
||||
|
||||
auto behaviorContext = componentApplicationRequests->GetBehaviorContext();
|
||||
|
||||
ASSERT_NE(behaviorContext, nullptr);
|
||||
|
||||
behaviorContext->Property("g_globalTemplateId", BehaviorValueProperty(&g_globalTemplateId));
|
||||
behaviorContext->Property("g_globalPrefabString", BehaviorValueProperty(&g_globalPrefabString));
|
||||
|
||||
g_globalTemplateId = TemplateId{};
|
||||
g_globalPrefabString = AZStd::string{};
|
||||
}
|
||||
|
||||
void SetUpEditorFixtureImpl() override
|
||||
{
|
||||
InitProperties();
|
||||
}
|
||||
|
||||
void TearDownEditorFixtureImpl() override
|
||||
{
|
||||
g_globalPrefabString.set_capacity(0); // Free all memory
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
entities = vector_EntityId()
|
||||
entities:push_back(my_id)
|
||||
g_globalTemplateId = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "test.prefab")
|
||||
)LUA");
|
||||
|
||||
EXPECT_NE(g_globalTemplateId, TemplateId{});
|
||||
|
||||
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
|
||||
|
||||
ASSERT_NE(prefabSystemComponentInterface, nullptr);
|
||||
|
||||
TemplateReference templateRef = prefabSystemComponentInterface->FindTemplate(g_globalTemplateId);
|
||||
|
||||
EXPECT_TRUE(templateRef);
|
||||
}
|
||||
|
||||
TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab_NoEntities)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
entities = vector_EntityId()
|
||||
g_globalTemplateId = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "test.prefab")
|
||||
)LUA");
|
||||
|
||||
EXPECT_NE(g_globalTemplateId, TemplateId{});
|
||||
|
||||
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
|
||||
|
||||
ASSERT_NE(prefabSystemComponentInterface, nullptr);
|
||||
|
||||
TemplateReference templateRef = prefabSystemComponentInterface->FindTemplate(g_globalTemplateId);
|
||||
|
||||
EXPECT_TRUE(templateRef);
|
||||
}
|
||||
|
||||
TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab_NoPath)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
sc.Execute(R"LUA(
|
||||
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
entities = vector_EntityId()
|
||||
template_id = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "")
|
||||
)LUA");
|
||||
/*
|
||||
error: PrefabSystemComponent::CreateTemplateFromInstance - Attempted to create a prefab template from an instance without a source file path. Unable to proceed.
|
||||
error: Failed to create a Template associated with file path during CreatePrefab.
|
||||
error: Failed to create prefab
|
||||
*/
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(3);
|
||||
}
|
||||
|
||||
TEST_F(PrefabScriptingTest, PrefabScripting_SaveToString)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
entities = vector_EntityId()
|
||||
entities:push_back(my_id)
|
||||
template_id = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "test.prefab")
|
||||
my_result = PrefabLoaderScriptingBus.Broadcast.SaveTemplateToString(template_id)
|
||||
|
||||
if my_result:IsSuccess() then
|
||||
g_globalPrefabString = my_result:GetValue()
|
||||
end
|
||||
)LUA");
|
||||
|
||||
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
|
||||
prefabSystemComponentInterface->RemoveAllTemplates();
|
||||
|
||||
EXPECT_STRNE(g_globalPrefabString.c_str(), "");
|
||||
TemplateId templateFromString = AZ::Interface<PrefabLoaderInterface>::Get()->LoadTemplateFromString(g_globalPrefabString);
|
||||
|
||||
EXPECT_NE(templateFromString, InvalidTemplateId);
|
||||
|
||||
// Create another entity for comparison purposes
|
||||
AZ::EntityId entityId;
|
||||
AzToolsFramework::EntityUtilityBus::BroadcastResult(
|
||||
entityId, &AzToolsFramework::EntityUtilityBus::Events::CreateEditorReadyEntity, "test");
|
||||
|
||||
AZ::Entity* testEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entityId);
|
||||
|
||||
// Instantiate the prefab we saved
|
||||
AZStd::unique_ptr<Instance> instance = prefabSystemComponentInterface->InstantiatePrefab(templateFromString);
|
||||
|
||||
EXPECT_NE(instance, nullptr);
|
||||
|
||||
AZStd::vector<const AZ::Entity*> loadedEntities;
|
||||
|
||||
// Get the entities from the instance
|
||||
instance->GetConstEntities(
|
||||
[&loadedEntities](const AZ::Entity& entity)
|
||||
{
|
||||
loadedEntities.push_back(&entity);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Make sure the instance has an entity with the same number of components as our test entity
|
||||
EXPECT_EQ(loadedEntities.size(), 1);
|
||||
EXPECT_EQ(loadedEntities[0]->GetComponents().size(), testEntity->GetComponents().size());
|
||||
|
||||
g_globalPrefabString.set_capacity(0); // Free all memory
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
||||
#include <Entity/EntityUtilityComponent.h>
|
||||
|
||||
#include <Prefab/PrefabTestComponent.h>
|
||||
#include <Prefab/PrefabTestDomUtils.h>
|
||||
#include <Prefab/PrefabTestFixture.h>
|
||||
#include <Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class ProceduralPrefabAssetTest
|
||||
: public PrefabTestFixture
|
||||
{
|
||||
void SetUpEditorFixtureImpl() override
|
||||
{
|
||||
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
|
||||
ASSERT_NE(componentApplicationRequests, nullptr);
|
||||
|
||||
auto* behaviorContext = componentApplicationRequests->GetBehaviorContext();
|
||||
ASSERT_NE(behaviorContext, nullptr);
|
||||
|
||||
auto* jsonRegistrationContext = componentApplicationRequests->GetJsonRegistrationContext();
|
||||
ASSERT_NE(jsonRegistrationContext, nullptr);
|
||||
|
||||
auto* serializeContext = componentApplicationRequests->GetSerializeContext();
|
||||
ASSERT_NE(serializeContext, nullptr);
|
||||
|
||||
AZ::Prefab::ProceduralPrefabAsset::Reflect(serializeContext);
|
||||
AZ::Prefab::ProceduralPrefabAsset::Reflect(behaviorContext);
|
||||
AZ::Prefab::ProceduralPrefabAsset::Reflect(jsonRegistrationContext);
|
||||
}
|
||||
|
||||
void TearDownEditorFixtureImpl() override
|
||||
{
|
||||
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
|
||||
componentApplicationRequests->GetJsonRegistrationContext()->EnableRemoveReflection();
|
||||
AZ::Prefab::ProceduralPrefabAsset::Reflect(componentApplicationRequests->GetJsonRegistrationContext());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ProceduralPrefabAssetTest, ReflectContext_AccessMethods_Works)
|
||||
{
|
||||
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
|
||||
|
||||
auto* serializeContext = componentApplicationRequests->GetSerializeContext();
|
||||
EXPECT_TRUE(!serializeContext->CreateAny(azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>()).empty());
|
||||
EXPECT_TRUE(!serializeContext->CreateAny(azrtti_typeid<AZ::Prefab::PrefabDomData>()).empty());
|
||||
|
||||
auto* jsonRegistrationContext = componentApplicationRequests->GetJsonRegistrationContext();
|
||||
EXPECT_TRUE(jsonRegistrationContext->GetSerializerForSerializerType(azrtti_typeid<AZ::Prefab::PrefabDomDataJsonSerializer>()));
|
||||
}
|
||||
|
||||
TEST_F(ProceduralPrefabAssetTest, ProceduralPrefabAsset_AccessMethods_Works)
|
||||
{
|
||||
const auto templateId = TemplateId(1);
|
||||
const auto prefabString = "fake.prefab";
|
||||
|
||||
AZ::Prefab::ProceduralPrefabAsset asset{};
|
||||
asset.SetTemplateId(templateId);
|
||||
EXPECT_EQ(asset.GetTemplateId(), templateId);
|
||||
|
||||
asset.SetTemplateName(prefabString);
|
||||
EXPECT_EQ(asset.GetTemplateName(), prefabString);
|
||||
}
|
||||
|
||||
TEST_F(ProceduralPrefabAssetTest, PrefabDomData_AccessMethods_Works)
|
||||
{
|
||||
AzToolsFramework::Prefab::PrefabDom dom;
|
||||
dom.SetObject();
|
||||
dom.AddMember("boolValue", true, dom.GetAllocator());
|
||||
|
||||
AZ::Prefab::PrefabDomData prefabDomData;
|
||||
prefabDomData.CopyValue(dom);
|
||||
|
||||
const AzToolsFramework::Prefab::PrefabDom& result = prefabDomData.GetValue();
|
||||
EXPECT_TRUE(result.HasMember("boolValue"));
|
||||
EXPECT_TRUE(result.FindMember("boolValue")->value.GetBool());
|
||||
}
|
||||
|
||||
TEST_F(ProceduralPrefabAssetTest, PrefabDomDataJsonSerializer_Load_Works)
|
||||
{
|
||||
AZ::Prefab::PrefabDomData prefabDomData;
|
||||
|
||||
AzToolsFramework::Prefab::PrefabDom dom;
|
||||
dom.SetObject();
|
||||
dom.AddMember("member", "value", dom.GetAllocator());
|
||||
|
||||
AZ::Prefab::PrefabDomDataJsonSerializer prefabDomDataJsonSerializer;
|
||||
AZ::JsonDeserializerSettings settings;
|
||||
settings.m_reporting = [](auto, auto, auto)
|
||||
{
|
||||
AZ::JsonSerializationResult::ResultCode result(AZ::JsonSerializationResult::Tasks::ReadField);
|
||||
return result;
|
||||
};
|
||||
AZ::JsonDeserializerContext context{ settings };
|
||||
|
||||
auto result = prefabDomDataJsonSerializer.Load(&prefabDomData, azrtti_typeid(prefabDomData), dom, context);
|
||||
EXPECT_EQ(result.GetResultCode().GetOutcome(), AZ::JsonSerializationResult::Outcomes::DefaultsUsed);
|
||||
EXPECT_TRUE(prefabDomData.GetValue().HasMember("member"));
|
||||
EXPECT_STREQ(prefabDomData.GetValue().FindMember("member")->value.GetString(), "value");
|
||||
}
|
||||
|
||||
TEST_F(ProceduralPrefabAssetTest, PrefabDomDataJsonSerializer_Store_Works)
|
||||
{
|
||||
AzToolsFramework::Prefab::PrefabDom dom;
|
||||
dom.SetObject();
|
||||
dom.AddMember("member", "value", dom.GetAllocator());
|
||||
|
||||
AZ::Prefab::PrefabDomData prefabDomData;
|
||||
prefabDomData.CopyValue(dom);
|
||||
|
||||
AZ::Prefab::PrefabDomDataJsonSerializer prefabDomDataJsonSerializer;
|
||||
AzToolsFramework::Prefab::PrefabDom outputValue;
|
||||
AZ::JsonSerializerSettings settings;
|
||||
settings.m_reporting = [](auto, auto, auto)
|
||||
{
|
||||
AZ::JsonSerializationResult::ResultCode result(AZ::JsonSerializationResult::Tasks::WriteValue);
|
||||
return result;
|
||||
};
|
||||
AZ::JsonSerializerContext context{ settings, outputValue.GetAllocator() };
|
||||
auto result = prefabDomDataJsonSerializer.Store(outputValue, &prefabDomData, nullptr, azrtti_typeid(prefabDomData), context);
|
||||
EXPECT_EQ(result.GetResultCode().GetOutcome(), AZ::JsonSerializationResult::Outcomes::DefaultsUsed);
|
||||
EXPECT_TRUE(outputValue.HasMember("member"));
|
||||
EXPECT_STREQ(outputValue.FindMember("member")->value.GetString(), "value");
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ set(FILES
|
||||
Entity/EditorEntityHelpersTests.cpp
|
||||
Entity/EditorEntitySearchComponentTests.cpp
|
||||
Entity/EditorEntitySelectionTests.cpp
|
||||
Entity/EntityUtilityComponentTests.cpp
|
||||
EntityIdQLabelTests.cpp
|
||||
EntityInspectorTests.cpp
|
||||
EntityOwnershipService/EntityOwnershipServiceTestFixture.cpp
|
||||
@@ -91,6 +92,8 @@ set(FILES
|
||||
Prefab/SpawnableSortEntitiesTestFixture.cpp
|
||||
Prefab/SpawnableSortEntitiesTestFixture.h
|
||||
Prefab/SpawnableSortEntitiesTests.cpp
|
||||
Prefab/PrefabScriptingTests.cpp
|
||||
Prefab/ProceduralPrefabAssetTests.cpp
|
||||
PropertyIntCtrlCommonTests.h
|
||||
PropertyIntSliderCtrlTests.cpp
|
||||
PropertyIntSpinCtrlTests.cpp
|
||||
|
||||
Reference in New Issue
Block a user