Integrating latest 47acbe8

This commit is contained in:
alexpete
2021-03-25 13:57:57 -07:00
parent 448c549698
commit 75dc720198
10312 changed files with 2711566 additions and 671451 deletions
@@ -349,7 +349,7 @@ namespace AZ
// Update the currentGameSpecialization
currentGameSpecialization = specializationKey;
// Update all the runtime filepaths based on the new "sys_game_folder" value
// Update all the runtime filepaths based on the new "sys_game_folder" value
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
}
}
@@ -460,6 +460,23 @@ namespace AZ
//=========================================================================
void ComponentApplication::CreateCommon()
{
{
AZ::SettingsRegistryInterface::FixedValueString registryValue;
m_settingsRegistry->Get(registryValue, AZ::SettingsRegistryMergeUtils::FilePathKey_DevWriteStorage);
AZ::IO::FixedMaxPath outputPath{ registryValue };
outputPath /= "eventlogger";
registryValue.clear();
AZ::IO::FixedMaxPathString baseFileName{ "EventLog" }; // default name
if (m_settingsRegistry->Get(registryValue, AZ::SettingsRegistryMergeUtils::BuildTargetNameKey))
{
baseFileName = registryValue;
}
m_eventLogger.Start(outputPath.c_str(), baseFileName.c_str());
}
CreateDrillers();
Sfmt::Create();
@@ -584,6 +601,8 @@ namespace AZ
m_drillerManager = nullptr;
}
m_eventLogger.Stop();
// Clear the descriptor to deallocate all strings (owned by ModuleDescriptor)
m_descriptor = Descriptor();
@@ -997,7 +1016,7 @@ namespace AZ
AZ::OSString m_dynamicLibraryPath;
bool m_autoLoad{ true };
};
struct GemModuleVisitor
: AZ::SettingsRegistryInterface::Visitor
{
@@ -1020,7 +1039,7 @@ namespace AZ
{
return gemName == moduleLoadData.m_gemName;
};
if (auto foundIt = AZStd::find_if(m_modulesLoadData.begin(), m_modulesLoadData.end(), FindGemModuleLoadEntry);
foundIt == m_modulesLoadData.end())
{
@@ -17,6 +17,7 @@
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Debug/ProfileModuleInit.h>
#include <AzCore/Debug/LocalFileEventLogger.h>
#include <AzCore/Memory/AllocationRecords.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Module/DynamicModuleHandle.h>
@@ -269,7 +270,7 @@ namespace AZ
void ResolveModulePath(AZ::OSString& modulePath) override;
void QueryApplicationType(AZ::ApplicationTypeQuery& appType) const override;
/**
* Returns Parsed CommandLine structure which supports query command line options and positional parameters
*/
@@ -282,12 +283,12 @@ namespace AZ
* and on some operating systems (MacOS) its fairly difficult to reliably retrieve them without resorting to NS libraries
* and making some assumptions. Instead, we allow you to pass your args in from the main(...) function.
* Another thing to notice here is that these are non-const pointers to the argc and argv values
* instead of int, char**, these are int*, char***.
* This is because some application layers (such as Qt) actually require that the ArgC and ArgV are modifiable,
* instead of int, char**, these are int*, char***.
* This is because some application layers (such as Qt) actually require that the ArgC and ArgV are modifiable,
* as they actually patch them to add/remove command line parameters during initialization.
* but also to highlight the fact that they are pointers to static memory that must remain relevant throughout the existence
* of the Application object.
* For best results, simply pass in &argc and &argv from your void main(argc, argv) in here - that memory is
* For best results, simply pass in &argc and &argv from your void main(argc, argv) in here - that memory is
* permanently tied to your process and is going to be available at all times during run.
*/
int* GetArgC();
@@ -368,7 +369,7 @@ namespace AZ
/**
* Check/verify a given path for the engine marker (file) so that we can identify that
* a given path is the engine root. This is only valid for target platforms that are built
* for the host platform and not deployable (ie windows, mac).
* for the host platform and not deployable (ie windows, mac).
* @param fullPath The full path to look for the engine marker
* @return true if the input path contains the engine marker file, false if not
*/
@@ -422,6 +423,11 @@ namespace AZ
AZ::CommandLine m_commandLine; // < Stores parsed command line supplied to the constructor
AZStd::unique_ptr<AZ::Entity> m_systemEntity; ///< Track the system entity to ensure we free it on shutdown.
// Created early to allow events to be logged before anything else. These will be kept in memory until
// a file is associated with the logger. The internal buffer is limited to 64kb and once full unexpected
// behavior may happen. The LocalFileEventLogger will register itself automatically with AZ::Interface<IEventLogger>.
AZ::Debug::LocalFileEventLogger m_eventLogger;
};
}
@@ -13,6 +13,7 @@
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/EntityBus.h>
#include <AzCore/Component/EntityIdSerializer.h>
#include <AzCore/Component/EntitySerializer.h>
#include <AzCore/Component/EntityUtils.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/TransformBus.h>
@@ -71,38 +72,27 @@ namespace AZ
}
};
Entity::Entity(const char* name)
: m_state(State::Constructed)
, m_transform(nullptr)
, m_isDependencyReady(false)
, m_isRuntimeActiveByDefault(true)
//=========================================================================
// Entity
// [5/31/2012]
//=========================================================================
Entity::Entity(AZStd::string name)
: Entity(MakeId(), AZStd::move(name))
{
m_id = MakeId();
if (name)
{
m_name = name;
}
else
{
to_string(m_name, static_cast<u64>(m_id));
}
}
Entity::Entity(const EntityId& id, const char* name)
//=========================================================================
// Entity
// [5/30/2012]
//=========================================================================
Entity::Entity(const EntityId& id, AZStd::string name)
: m_id(id)
, m_state(State::Constructed)
, m_transform(nullptr)
, m_name{ name.empty() ? AZStd::to_string(static_cast<u64>(m_id)) : AZStd::move(name) }
, m_state(State::Constructed)
, m_isDependencyReady(false)
, m_isRuntimeActiveByDefault(true)
{
if (name)
{
m_name = name;
}
else
{
to_string(m_name, static_cast<u64>(m_id));
}
}
Entity::~Entity()
@@ -825,6 +815,7 @@ namespace AZ
AZ::JsonRegistrationContext* jsonRegistration = azrtti_cast<AZ::JsonRegistrationContext*>(reflection);
if (jsonRegistration)
{
jsonRegistration->Serializer<JsonEntitySerializer>()->HandlesType<Entity>();
jsonRegistration->Serializer<JsonEntityIdSerializer>()->HandlesType<EntityId>();
}
}
@@ -1153,8 +1144,8 @@ namespace AZ
{
if (processingRequiredServices)
{
return FailureCode(DependencySortResult::MissingRequiredService, "Component '%s' is missing another required component.",
componentInfo.m_component->RTTI_GetTypeName());
return FailureCode(DependencySortResult::MissingRequiredService, "Component '%s' is missing another required service: 0x%0x",
componentInfo.m_component->RTTI_GetTypeName(), service);
}
else
{
@@ -34,6 +34,8 @@ namespace AZ
//! An entity has an ID and, optionally, a name.
class Entity
{
friend class JsonEntitySerializer;
public:
//! Specifies that this class should use AZ::SystemAllocator for memory management by default.
@@ -84,16 +86,20 @@ namespace AZ
DSR_CYCLIC_DEPENDENCY = HasCyclicDependency,
};
//! Constructs an entity and automatically generates an entity ID.
//! @param name (Optional) A name for the entity. The entity ID is used for addressing and identification,
//! but a name is useful for debugging.
Entity(const char* name = nullptr);
/**
* Constructs an entity and automatically generates an entity ID.
* @param name (Optional) A name for the entity. The entity ID is used for addressing and identification,
* but a name is useful for debugging.
*/
explicit Entity(AZStd::string name = {});
//! Constructs an entity with the entity ID that you specify.
//! @param id An ID for the entity.
//! @param name (Optional) A name for the entity. The entity ID is used for addressing and identification,
//! but a name is useful for debugging.
Entity(const EntityId& id, const char* name = nullptr);
/**
* Constructs an entity with the entity ID that you specify.
* @param id An ID for the entity.
* @param name (Optional) A name for the entity. The entity ID is used for addressing and identification,
* but a name is useful for debugging.
*/
explicit Entity(const EntityId& id, AZStd::string name = {});
// Delete the copy constructor, because this contains vector of pointers and other pointers that
// are supposed to be unique, this would be a mistake. Its safer to cause code that tries to
@@ -31,6 +31,11 @@ namespace AZ
virtual JsonSerializationResult::Result MapJsonToId(EntityId& outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context) = 0;
virtual JsonSerializationResult::Result MapIdToJson(rapidjson::Value& outputValue, const EntityId& inputValue, JsonSerializerContext& context) = 0;
inline void SetIsEntityReference(bool isEntityReference) { m_isEntityReference = isEntityReference; };
protected:
bool m_isEntityReference = true;
};
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
@@ -0,0 +1,186 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/EntityIdSerializer.h>
#include <AzCore/Component/EntitySerializer.h>
namespace AZ
{
AZ_CLASS_ALLOCATOR_IMPL(JsonEntitySerializer, AZ::SystemAllocator, 0);
JsonSerializationResult::Result JsonEntitySerializer::Load(void* outputValue, [[maybe_unused]] const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult;
AZ_Assert(azrtti_typeid<AZ::Entity>() == outputValueTypeId, "Unable to deserialize Entity from json because the provided type is %s.",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ::Entity* entityInstance = reinterpret_cast<AZ::Entity*>(outputValue);
AZ_Assert(entityInstance, "Output value for JsonEntitySerializer can't be null.");
JsonEntityIdSerializer::JsonEntityIdMapper** idMapper = context.GetMetadata().Find<JsonEntityIdSerializer::JsonEntityIdMapper*>();
bool hasValidIdMapper = idMapper && *idMapper;
JSR::ResultCode result(JSR::Tasks::ReadField);
{
if (hasValidIdMapper)
{
(*idMapper)->SetIsEntityReference(false);
}
JSR::ResultCode idLoadResult =
ContinueLoadingFromJsonObjectField(&entityInstance->m_id,
azrtti_typeid<decltype(entityInstance->m_id)>(),
inputValue, "Id", context);
if (hasValidIdMapper)
{
(*idMapper)->SetIsEntityReference(true);
}
result.Combine(idLoadResult);
}
{
JSR::ResultCode nameLoadResult =
ContinueLoadingFromJsonObjectField(&entityInstance->m_name,
azrtti_typeid<decltype(entityInstance->m_name)>(),
inputValue, "Name", context);
result.Combine(nameLoadResult);
}
{
JSR::ResultCode componentLoadResult =
ContinueLoadingFromJsonObjectField(&entityInstance->m_components,
azrtti_typeid<decltype(entityInstance->m_components)>(),
inputValue, "Components", context);
result.Combine(componentLoadResult);
}
{
JSR::ResultCode dependencyReadyLoadResult =
ContinueLoadingFromJsonObjectField(&entityInstance->m_isDependencyReady,
azrtti_typeid<decltype(entityInstance->m_isDependencyReady)>(),
inputValue, "IsDependencyReady", context);
result.Combine(dependencyReadyLoadResult);
}
{
JSR::ResultCode runtimeActiveLoadResult =
ContinueLoadingFromJsonObjectField(&entityInstance->m_isRuntimeActiveByDefault,
azrtti_typeid<decltype(entityInstance->m_isRuntimeActiveByDefault)>(),
inputValue, "IsRuntimeActive", context);
}
return context.Report(result,
result.GetProcessing() == JSR::Processing::Halted ? "Succesfully loaded entity information." :
"Failed to load entity information.");
}
JsonSerializationResult::Result JsonEntitySerializer::Store(rapidjson::Value& outputValue,
const void* inputValue, const void* defaultValue, [[maybe_unused]] const Uuid& valueTypeId, JsonSerializerContext& context)
{
namespace JSR = AZ::JsonSerializationResult;
AZ_Assert(azrtti_typeid<AZ::Entity>() == valueTypeId, "Unable to Serialize Entity because the provided type is %s.",
valueTypeId.ToString<AZStd::string>().c_str());
const AZ::Entity* entityInstance = reinterpret_cast<const AZ::Entity*>(inputValue);
AZ_Assert(entityInstance, "Input value for JsonEntitySerializer can't be null.");
const AZ::Entity* defaultEntityInstance = reinterpret_cast<const AZ::Entity*>(defaultValue);
JsonEntityIdSerializer::JsonEntityIdMapper** idMapper = context.GetMetadata().Find<JsonEntityIdSerializer::JsonEntityIdMapper*>();
bool hasValidIdMapper = idMapper && *idMapper;
JSR::ResultCode result(JSR::Tasks::WriteValue);
{
AZ::ScopedContextPath subPathId(context, "m_id");
const AZ::EntityId* id = &entityInstance->m_id;
const AZ::EntityId* defaultId = defaultEntityInstance ? &defaultEntityInstance->m_id : nullptr;
if (hasValidIdMapper)
{
(*idMapper)->SetIsEntityReference(false);
}
result = ContinueStoringToJsonObjectField(outputValue, "Id",
id, defaultId, azrtti_typeid<decltype(entityInstance->m_id)>(), context);
if (hasValidIdMapper)
{
(*idMapper)->SetIsEntityReference(true);
}
}
{
AZ::ScopedContextPath subPathName(context, "m_name");
const AZStd::string* name = &entityInstance->m_name;
const AZStd::string* defaultName =
defaultEntityInstance ? &defaultEntityInstance->m_name : nullptr;
JSR::ResultCode resultName =
ContinueStoringToJsonObjectField(outputValue, "Name",
name, defaultName, azrtti_typeid<decltype(entityInstance->m_name)>(), context);
result.Combine(resultName);
}
{
AZ::ScopedContextPath subPathComponents(context, "m_components");
const AZ::Entity::ComponentArrayType* components = &entityInstance->m_components;
const AZ::Entity::ComponentArrayType* defaultComponents =
defaultEntityInstance ? &defaultEntityInstance->m_components : nullptr;
JSR::ResultCode resultComponents =
ContinueStoringToJsonObjectField(outputValue, "Components",
components, defaultComponents, azrtti_typeid<decltype(entityInstance->m_components)>(), context);
result.Combine(resultComponents);
}
{
AZ::ScopedContextPath subPathDependencyReady(context, "m_isDependencyReady");
const bool* dependencyReady = &entityInstance->m_isDependencyReady;
const bool* dependencyReadyDefault =
defaultEntityInstance ? &defaultEntityInstance->m_isDependencyReady : nullptr;
JSR::ResultCode resultDependencyReady =
ContinueStoringToJsonObjectField(outputValue, "IsDependencyReady",
dependencyReady, dependencyReadyDefault,
azrtti_typeid<decltype(entityInstance->m_isDependencyReady)>(), context);
result.Combine(resultDependencyReady);
}
{
AZ::ScopedContextPath subPathRuntimeActive(context, "m_isRuntimeActiveByDefault");
const bool* runtimeActive = &entityInstance->m_isRuntimeActiveByDefault;
const bool* runtimeActiveDefault =
defaultEntityInstance ? &defaultEntityInstance->m_isRuntimeActiveByDefault : nullptr;
JSR::ResultCode resultRuntimeActive =
ContinueStoringToJsonObjectField(outputValue, "IsRuntimeActive",
runtimeActive, runtimeActiveDefault, azrtti_typeid<decltype(entityInstance->m_isRuntimeActiveByDefault)>(), context);
result.Combine(resultRuntimeActive);
}
return context.Report(result,
result.GetProcessing() == JSR::Processing::Halted ? "Successfully stored Entity information." :
"Failed to store Entity information.");
}
}
@@ -0,0 +1,33 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
namespace AZ
{
class JsonEntitySerializer
: public BaseJsonSerializer
{
public:
AZ_RTTI(JsonEntitySerializer, "{015BBF46-E21A-41AA-816A-C63119FB2852}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const Uuid& valueTypeId,
JsonSerializerContext& context) override;
};
}
@@ -19,6 +19,9 @@ namespace AZ
{
class Vector3;
//! Do not allow the scale to be zero to avoid problems with inverting scale.
static constexpr float MinNonUniformScale = 1e-3f;
using NonUniformScaleChangedEvent = AZ::Event<const AZ::Vector3&>;
//! Requests for working with non-uniform scale.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,137 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <type_traits>
#include <AzCore/base.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/std/limits.h>
#include <AzCore/std/string/string_view.h>
namespace AZ::Debug
{
//! Simple hash structure based on DJB2a to generate event IDs at compile time
class EventNameHash
{
public:
constexpr explicit EventNameHash(AZStd::string_view name)
{
for (auto c : name)
{
m_hash = static_cast<uint32_t>(((static_cast<uint64_t>(m_hash) << 5) + m_hash) ^ c);
}
}
constexpr bool operator==(const EventNameHash& rhs) const
{
return m_hash == rhs.m_hash;
}
constexpr bool operator!=(const EventNameHash& rhs) const
{
return m_hash != rhs.m_hash;
}
private:
uint32_t m_hash{ 5381 }; // standard starting value for DJB2a hash
};
constexpr EventNameHash PrologEventHash("Prolog");
constexpr uint16_t EventBoundary = 8;
class IEventLogger
{
public:
struct LogHeader
{
int8_t m_4cc[4]{ 'A', 'Z', 'E', 'L' }; //!< 4CC to uniquely identify the data type. Defaults to 'AZEL'
uint32_t m_majorVersion{ 1 }; //!< Major version of the log format.
uint32_t m_minorVersion{ 0 }; //!< Minor version of the log format.
//! A user defined version. This will always be zero but allows users
//! make modifications without needing to change the main version number
//! which in turn makes integrations easier.
uint32_t m_userVersion{ 0 };
};
struct EventHeader
{
EventNameHash m_eventId; //!< Unique id that identifies the event. This is typically a hash of the event name.
uint16_t m_size; //!< The size of the event. Events can be up to 64Kib large.
//! Event specific flags set by the caller. The flags can be to reuse the same event with a slight
//! alteration, such as a begin/end pair. If two similar have different data, such as a begin
//! having a bit of extra data that the end doesn't have, then it's recommended to create two unique
//! events instead to keep the log small.
uint16_t m_flags;
};
struct Prolog : public EventHeader
{
uint64_t m_threadId; //!< Unique id of the thread the log buffer is being recorded on.
};
AZ_TYPE_INFO(AZ::Debug::IEventLogger, "{D39D09FA-DEA0-4874-BC45-4B310C3DD52E}");
virtual ~IEventLogger() = default;
//! Writes and flushes all thread local buffers to disk and flushes the disk to store the recorded events.
virtual void Flush() = 0;
//! Starts a new event. If there is not enough room left in the thread local buffer then the buffer will be stored to disk and cleared.
//! @param id Id that uniquely identifies this event.
//! @param size The total size of the event, excluding the event header. Typically this is the size of the structure that describes the event.
//! @param flags Optional flags unique to the event. For instance a "Thread" event can use the flags to indicate whether the
//! the thread is starting or stopping.
//! @return A void pointer to reserved data in the thread local buffer to write to.
virtual void* RecordEventBegin(EventNameHash id, uint16_t size, uint16_t flags = 0) = 0;
//! End a previously started event. After calling RecordEventBegin, flushing will not be possible until RecordEventEnd is called.
virtual void RecordEventEnd() = 0;
//! Utility function to write an event that only has a string.
//! @param id Id that uniquely identifies this event.
//! @param text The string that will be logged.
//! @param flags Optional flags unique to the event.
virtual void RecordStringEvent(EventNameHash id, AZStd::string_view text, uint16_t flags = 0) = 0;
//! Utility function to begin an event with a specific structure.
//! For example this can be used as:
//! struct ThreadInfo
//! {
//! uint64_t m_threadId;
//! uint64_t m_processorId;
//! };
//! auto& info = RecordEventBegin<ThreadInfo>("ThreadInfo");
//! info.m_threadId = ...;
//! info.m_processorId = ...;
//! RecordEventEnd();
//! @param id Id that uniquely identifies this event.
//! @param flags Optional flags unique to the event.
//! @return A reference of the provided type to store event information in.
template<typename T>
T& RecordEventBegin(EventNameHash id, uint16_t flags = 0);
};
template<typename T>
T& IEventLogger::RecordEventBegin(EventNameHash id, uint16_t flags)
{
constexpr size_t typeSize = sizeof(T);
static_assert(AZStd::is_trivially_copyable_v<T>, "Only simple classes can be added to the event logger.");
static_assert(typeSize <= AZStd::numeric_limits<decltype(EventHeader::m_size)>::max(), "Class too large to store with the event logger.");
void* eventData = RecordEventBegin(id, aznumeric_cast<uint16_t>(typeSize), flags);
return *reinterpret_cast<T*>(eventData);
}
} // namespace AZ::Debug
@@ -0,0 +1,368 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Platform.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Debug/LocalFileEventLogger.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/parallel/scoped_lock.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <time.h>
namespace AZ::Debug
{
static constexpr const char* RegistryKey_TimestampLogFiles = "/Amazon/AzCore/EventLogger/TimestampLogFiles";
//
// EventLogReader
//
bool EventLogReader::ReadLog(const char* filePath)
{
using namespace AZ::IO;
if (SystemFile::Exists(filePath))
{
SystemFile::SizeType size = SystemFile::Length(filePath);
if (size == 0)
{
return false;
}
m_buffer.resize_no_construct(size);
uint8_t* buffer = m_buffer.data();
SystemFile::SizeType readSize = SystemFile::Read(filePath, buffer, size);
if (readSize == size)
{
memcpy(&m_logHeader, buffer, sizeof(m_logHeader));
m_current = reinterpret_cast<IEventLogger::EventHeader*>(buffer + sizeof(m_logHeader));
UpdateThreadId();
return true;
}
}
return false;
}
EventNameHash EventLogReader::GetEventName() const
{
return m_current->m_eventId;
}
uint16_t EventLogReader::GetEventSize() const
{
return m_current->m_size;
}
uint16_t EventLogReader::GetEventFlags() const
{
return m_current->m_flags;
}
uint64_t EventLogReader::GetThreadId() const
{
return m_currentThreadId;
}
AZStd::string_view EventLogReader::GetString() const
{
return AZStd::string_view(reinterpret_cast<char*>(m_current + 1), m_current->m_size);
}
bool EventLogReader::Next()
{
size_t increment = AZ_SIZE_ALIGN_UP(sizeof(IEventLogger::EventHeader) + m_current->m_size, EventBoundary);
uint8_t* bufferPosition = reinterpret_cast<uint8_t*>(m_current) + increment;
if (bufferPosition < m_buffer.end())
{
m_current = reinterpret_cast<IEventLogger::EventHeader*>(bufferPosition);
UpdateThreadId();
return true;
}
return false;
}
void EventLogReader::UpdateThreadId()
{
if (GetEventName() == PrologEventHash)
{
auto prolog = reinterpret_cast<IEventLogger::Prolog*>(m_current);
m_currentThreadId = prolog->m_threadId;
}
}
//
// LocalFileEventLogger
//
LocalFileEventLogger::~LocalFileEventLogger()
{
if (m_file.IsOpen())
{
Stop();
}
// thread blocks should have already been flushed above, so
// this is purely to clear the logger ownership safely
while (!m_threadDataBlocks.empty())
{
m_threadDataBlocks.back()->Reset(nullptr);
}
}
bool LocalFileEventLogger::Start(const AZ::IO::Path& filePath)
{
using namespace AZ::IO;
AZStd::scoped_lock lock(m_fileGuard);
if (m_file.Open(filePath.c_str(), SystemFile::SF_OPEN_WRITE_ONLY | SystemFile::SF_OPEN_CREATE | SystemFile::SF_OPEN_CREATE_PATH))
{
LogHeader defaultHeader;
m_file.Write(&defaultHeader, sizeof(LogHeader));
return true;
}
return false;
}
bool LocalFileEventLogger::Start(AZStd::string_view outputPath, AZStd::string_view fileNameHint)
{
using namespace AZ::IO;
FixedMaxPath filePath{ outputPath };
FixedMaxPathString fileName{ fileNameHint };
SystemFile::CreateDir(filePath.c_str());
bool includeTimestamp = false;
AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
settingsRegistry->Get(includeTimestamp, RegistryKey_TimestampLogFiles);
if (includeTimestamp)
{
time_t rawtime;
time(&rawtime);
tm timeinfo;
azlocaltime(&rawtime, &timeinfo);
constexpr int timestampSize = 64;
char timestamp[timestampSize]{ 0 };
// based the ISO-8601 standard (YYYY-MM-DDTHH-mm-ssTZD) e.g., 20210224_1122
strftime(timestamp, timestampSize, "%Y%m%d_%H%M", &timeinfo);
fileName = AZ::IO::FixedMaxPathString::format("%.*s_%s",
aznumeric_cast<int>(fileNameHint.size()), fileNameHint.data(),
timestamp);
}
filePath /= fileName;
filePath.ReplaceExtension("azel");
return Start(filePath.c_str());
}
void LocalFileEventLogger::Stop()
{
Flush();
m_file.Close();
}
void LocalFileEventLogger::Flush()
{
// Create new storage for a thread to write to. This will replace the storage already on the thread
// so it can continue to write and is not blocked during a flush. The data that was swapped in can
// then again be used for the next thread.
ThreadData* replacementData = new ThreadData();
bool flushedThread[MaxThreadCount] = {};
{
AZStd::scoped_lock fileGuardLock(m_fileGuard);
bool allFlushed;
do
{
allFlushed = true;
for (size_t i = 0; i < m_threadDataBlocks.size(); ++i)
{
// Don't flush threads that have already been flushed because during high activity
// this can cause this loop to always find more threads to flush resulting in
// taking a long time to exit the Flush function. As a side effect of this it will
// decrease the time between retrying a thread it previous failed to claim which
// increases the chance it gets to switch the data.
if (flushedThread[i])
{
continue;
}
ThreadStorage* thread = m_threadDataBlocks[i];
ThreadData* threadData = thread->m_data;
if (!threadData)
{
allFlushed = false;
continue;
}
// ensure the thread ID propagates after the exchange
replacementData->m_threadId = threadData->m_threadId;
if (!thread->m_data.compare_exchange_strong(threadData, replacementData))
{
// Since no other flush can reach this point due to the lock, failing this means
// that between looking up the address for the data and the swap the owning thread
// has started a write, so bail for now and come back to this one at a later time
// to try again.
allFlushed = false;
continue;
}
{
AZStd::scoped_lock fileWriteGuardLock(m_fileWriteGuard);
WriteCacheToDisk(*threadData);
}
replacementData = threadData;
flushedThread[i] = true;
}
} while (!allFlushed);
m_file.Flush();
}
delete replacementData;
}
void* LocalFileEventLogger::RecordEventBegin(EventNameHash id, uint16_t size, uint16_t flags)
{
ThreadStorage& threadStorage = GetThreadStorage();
ThreadData* threadData = threadStorage.m_data;
// Set to nullptr so other threads doing a flush can't pick this up.
while (!threadStorage.m_data.compare_exchange_strong(threadData, nullptr));
uint32_t writeSize = AZ_SIZE_ALIGN_UP(sizeof(EventHeader) + size, EventBoundary);
if (threadData->m_usedBytes + writeSize >= ThreadData::BufferSize)
{
AZStd::scoped_lock lock(m_fileWriteGuard);
WriteCacheToDisk(*threadData);
}
char* eventBuffer = (threadData->m_buffer + threadData->m_usedBytes);
EventHeader* header = reinterpret_cast<EventHeader*>(eventBuffer);
header->m_eventId = id;
header->m_size = size;
header->m_flags = flags;
threadData->m_usedBytes += writeSize;
// cache the event data so it doesn't get picked up by calls to flush
// before it has been committed
threadStorage.m_pendingData = threadData;
return (eventBuffer + sizeof(EventHeader));
}
void LocalFileEventLogger::RecordEventEnd()
{
// swap the pending data to commit the event
ThreadStorage& threadStorage = GetThreadStorage();
ThreadData* expectedData = nullptr;
while (!threadStorage.m_data.compare_exchange_strong(expectedData, threadStorage.m_pendingData));
threadStorage.m_pendingData = nullptr;
}
void LocalFileEventLogger::RecordStringEvent(EventNameHash id, AZStd::string_view text, uint16_t flags)
{
constexpr size_t maxSize = AZStd::numeric_limits<decltype(EventHeader::m_size)>::max();
const size_t stringLen = text.length();
if (stringLen > maxSize)
{
AZ_Assert(false, "Failed to write event! String too large to store with the event logger.");
return;
}
void* eventText = RecordEventBegin(id, aznumeric_cast<uint16_t>(stringLen), flags);
memcpy(eventText, text.data(), stringLen);
RecordEventEnd();
}
void LocalFileEventLogger::WriteCacheToDisk(ThreadData& threadData)
{
// ensure the front loaded prolog is accurate, ThreadData objects
// are recycled during flush
Prolog* prologHeader = reinterpret_cast<Prolog*>(threadData.m_buffer);
prologHeader->m_eventId = PrologEventHash;
prologHeader->m_size = sizeof(prologHeader->m_threadId);
prologHeader->m_flags = 0; // unused in the prolog
prologHeader->m_threadId = threadData.m_threadId;
m_file.Write(threadData.m_buffer, threadData.m_usedBytes);
threadData.m_usedBytes = sizeof(Prolog); // keep enough room for the next chunk's prolog
}
auto LocalFileEventLogger::GetThreadStorage()->ThreadStorage&
{
thread_local static ThreadStorage s_storage;
s_storage.Reset(this);
return s_storage;
}
//
// LocalFileEventLogger::ThreadStorage
//
LocalFileEventLogger::ThreadStorage::~ThreadStorage()
{
Reset(nullptr);
}
void LocalFileEventLogger::ThreadStorage::Reset(LocalFileEventLogger* owner)
{
if (m_owner == owner)
{
return;
}
if (m_owner)
{
AZStd::scoped_lock guard(m_owner->m_fileGuard);
// Save to access thread data because of the lock.
ThreadData* data = m_data;
if (data->m_usedBytes > 0)
{
m_owner->WriteCacheToDisk(*data);
}
auto it = AZStd::find(m_owner->m_threadDataBlocks.begin(), m_owner->m_threadDataBlocks.end(), this);
if (it != m_owner->m_threadDataBlocks.end())
{
m_owner->m_threadDataBlocks.erase(it);
}
delete data;
}
m_owner = owner;
if (m_owner)
{
// Deliberately using system memory instead of regular allocators. If debug allocators
// are available in the future those should be used instead.
ThreadData* data = new ThreadData();
data->m_threadId = azlossy_caster(AZStd::hash<AZStd::thread_id>{}(AZStd::this_thread::get_id()));
m_data = data;
AZStd::scoped_lock guard(m_owner->m_fileGuard);
m_owner->m_threadDataBlocks.push_back(this);
}
}
} // namespace AZ::Debug
@@ -0,0 +1,110 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <limits>
#include <AzCore/Debug/IEventLogger.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/containers/fixed_vector.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/string/string_view.h>
namespace AZ::Debug
{
class EventLogReader
{
public:
bool ReadLog(const char* filePath);
EventNameHash GetEventName() const;
uint16_t GetEventSize() const;
uint16_t GetEventFlags() const;
uint64_t GetThreadId() const;
AZStd::string_view GetString() const;
template<typename T>
const T* GetValue() const
{
AZ_Assert(sizeof(T) <= m_current->m_size, "Attempting to retrieve a value that's larger than the amount of stored data.");
return reinterpret_cast<T*>(m_current + 1);
}
bool Next();
private:
void UpdateThreadId();
AZStd::vector<uint8_t> m_buffer;
IEventLogger::LogHeader m_logHeader;
uint64_t m_currentThreadId{ 0 };
IEventLogger::EventHeader* m_current{ nullptr };
};
class LocalFileEventLogger
: public Interface<IEventLogger>::Registrar
{
public:
inline static constexpr size_t MaxThreadCount = 512;
~LocalFileEventLogger() override;
bool Start(const AZ::IO::Path& filePath);
bool Start(AZStd::string_view outputPath, AZStd::string_view fileNameHint);
void Stop();
void Flush() override;
void* RecordEventBegin(EventNameHash id, uint16_t size, uint16_t flags = 0) override;
void RecordEventEnd() override;
void RecordStringEvent(EventNameHash id, AZStd::string_view text, uint16_t flags = 0) override;
protected:
struct ThreadData
{
// ensure there is enough room for one large event with header + prolog
static constexpr size_t BufferSize = AZStd::numeric_limits<decltype(EventHeader::m_size)>::max() + sizeof(EventHeader) + sizeof(Prolog);
char m_buffer[BufferSize]{ 0 };
uint64_t m_threadId{ 0 };
uint32_t m_usedBytes{ sizeof(Prolog) }; // always front load the buffer with a prolog
};
struct ThreadStorage
{
ThreadStorage() = default;
~ThreadStorage();
void Reset(LocalFileEventLogger* owner);
AZStd::atomic<ThreadData*> m_data{ nullptr };
ThreadData* m_pendingData{ nullptr };
LocalFileEventLogger* m_owner{ nullptr };
};
void WriteCacheToDisk(ThreadData& threadData);
ThreadStorage& GetThreadStorage();
AZStd::fixed_vector<ThreadStorage*, MaxThreadCount> m_threadDataBlocks;
AZ::IO::SystemFile m_file;
AZStd::recursive_mutex m_fileGuard;
AZStd::recursive_mutex m_fileWriteGuard;
};
} // namespace AZ::Debug
@@ -17,6 +17,8 @@
#include <AzCore/Debug/StackTracer.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/Debug/TraceMessagesDrillerBus.h>
#include <AzCore/Debug/IEventLogger.h>
#include <AzCore/Interface/Interface.h>
#include <stdarg.h>
@@ -74,6 +76,11 @@ namespace AZ
static AZ::EnvironmentVariable<int> g_assertVerbosityLevel;
static AZ::EnvironmentVariable<int> g_logVerbosityLevel;
static constexpr auto PrintfEventId = EventNameHash("Printf");
static constexpr auto WarningEventId = EventNameHash("Warning");
static constexpr auto ErrorEventId = EventNameHash("Error");
static constexpr auto AssertEventId = EventNameHash("Assert");
constexpr LogLevel DefaultLogLevel = LogLevel::Info;
AZ_CVAR_SCOPED(int, bg_traceLogLevel, DefaultLogLevel, nullptr, ConsoleFunctorFlags::Null, "Enable trace message logging in release mode. 0=disabled, 1=errors, 2=warnings, 3=info.");
@@ -230,6 +237,12 @@ namespace AZ
azvsnprintf(message, g_maxMessageLength - 1, format, mark); // -1 to make room for the "/n" that will be appended below
va_end(mark);
if (auto logger = Interface<IEventLogger>::Get(); logger)
{
logger->RecordStringEvent(AssertEventId, message);
logger->Flush(); // Flush as an assert may indicate a crash is imminent.
}
EBUS_EVENT(TraceMessageDrillerBus, OnPreAssert, fileName, line, funcName, message);
TraceMessageResult result;
@@ -338,6 +351,11 @@ namespace AZ
azvsnprintf(message, g_maxMessageLength-1, format, mark); // -1 to make room for the "/n" that will be appended below
va_end(mark);
if (auto logger = Interface<IEventLogger>::Get(); logger)
{
logger->RecordStringEvent(ErrorEventId, message);
}
EBUS_EVENT(TraceMessageDrillerBus, OnPreError, window, fileName, line, funcName, message);
TraceMessageResult result;
@@ -385,6 +403,11 @@ namespace AZ
azvsnprintf(message, g_maxMessageLength - 1, format, mark); // -1 to make room for the "/n" that will be appended below
va_end(mark);
if (auto logger = Interface<IEventLogger>::Get(); logger)
{
logger->RecordStringEvent(WarningEventId, message);
}
EBUS_EVENT(TraceMessageDrillerBus, OnPreWarning, window, fileName, line, funcName, message);
TraceMessageResult result;
@@ -424,6 +447,11 @@ namespace AZ
azvsnprintf(message, g_maxMessageLength, format, mark);
va_end(mark);
if (auto logger = Interface<IEventLogger>::Get(); logger)
{
logger->RecordStringEvent(PrintfEventId, message);
}
EBUS_EVENT(TraceMessageDrillerBus, OnPrintf, window, message);
TraceMessageResult result;
+64 -46
View File
@@ -13,10 +13,11 @@
#pragma once
#include <AzCore/base.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/stack.h>
#include <AzCore/std/function/function_fwd.h>
#include <AzCore/Casting/numeric_cast.h>
namespace AZ
{
@@ -34,64 +35,31 @@ namespace AZ
//! event.Signal(1); // Our handlers lambda will now get invoked with the value 1
//! };
//! @endcode
template <typename... Params>
class Event;
template <typename... Params>
class EventHandler;
template <typename... Params>
class Event final
{
friend class EventHandler<Params...>; // This is required in order to allow for relocating the handle pointers on a move
public:
using Callback = AZStd::function<void(Params...)>;
using Handler = EventHandler<Params...>;
//! A handler class that can connect to an Event
class Handler final
{
friend class Event;
public:
// We support default constructing of event handles (with no callback function being bound) to allow for better usage with container types
// An unbound event handle cannot be added to an event and we do not support dynamically binding the callback post construction
// (except for on assignment since that will also add the handle to the event; i.e. there is no way to unbind the callback after being added to an event)
Handler() = default;
explicit Handler(std::nullptr_t);
explicit Handler(Callback callback);
Handler(const Handler& rhs);
Handler(Handler&& rhs);
~Handler();
Handler& operator=(const Handler& rhs);
Handler& operator=(Handler&& rhs);
//! Connects the handler to the provided event.
//! @param event the Event to connect to
void Connect(Event<Params...>& event);
//! Disconnects the handler from its connected event, does nothing if the event is not connected.
void Disconnect();
//! Returns true if this handler is connected to an event.
//! @return boolean true if this handler is connected to an event
bool IsConnected() const;
private:
//! Swaps the event handler pointers from the from instance to this instance
//! @param from the handler instance we are replacing on the attached event
void SwapEventHandlerPointers(const Handler& from);
const Event<Params...>* m_event = nullptr; //< The connected event
int32_t m_index = 0; //< Index into the add or handler vectors (negative means pending add)
Callback m_callback; //< The lambda to invoke during events
};
AZ_CLASS_ALLOCATOR(Event<Params...>, AZ::SystemAllocator, 0);
Event() = default;
Event(Event&& rhs);
Event(const Event& rhs) = delete; // Cannot copy events
~Event();
Event& operator=(Event&& rhs);
Event& operator=(const Event& rhs) = delete; // Cannot copy events
//! Returns true if at least one handler is connected to this event.
bool HasHandlerConnected() const;
@@ -120,6 +88,56 @@ namespace AZ
mutable bool m_updating = false; //< Raised during a Signal, false otherwise, used to guard m_handlers during handler iteration
};
//! A handler class that can connect to an Event
template <typename... Params>
class EventHandler final
{
friend class Event<Params...>;
public:
using Callback = AZStd::function<void(Params...)>;
AZ_CLASS_ALLOCATOR(EventHandler<Params...>, AZ::SystemAllocator, 0);
// We support default constructing of event handles (with no callback function being bound) to allow for better usage with container types
// An unbound event handle cannot be added to an event and we do not support dynamically binding the callback post construction
// (except for on assignment since that will also add the handle to the event; i.e. there is no way to unbind the callback after being added to an event)
EventHandler() = default;
explicit EventHandler(std::nullptr_t);
explicit EventHandler(Callback callback);
EventHandler(const EventHandler& rhs);
EventHandler(EventHandler&& rhs);
~EventHandler();
EventHandler& operator=(const EventHandler& rhs);
EventHandler& operator=(EventHandler&& rhs);
//! Connects the handler to the provided event.
//! @param event the Event to connect to
void Connect(Event<Params...>& event);
//! Disconnects the handler from its connected event, does nothing if the event is not connected.
void Disconnect();
//! Returns true if this handler is connected to an event.
//! @return boolean true if this handler is connected to an event
bool IsConnected() const;
private:
//! Swaps the event handler pointers from the from instance to this instance
//! @param from the handler instance we are replacing on the attached event
void SwapEventHandlerPointers(const EventHandler& from);
const Event<Params...>* m_event = nullptr; //< The connected event
int32_t m_index = 0; //< Index into the add or handler vectors (negative means pending add)
Callback m_callback; //< The lambda to invoke during events
};
AZ_TYPE_INFO_INTERNAL_SPECIALIZED_TEMPLATE_PREFIX_UUID(AZ::Event, "Event", "{B7388760-18BF-486A-BE96-D5765791C53C}", AZ_TYPE_INFO_INTERNAL_TYPENAME_VARARGS);
AZ_TYPE_INFO_INTERNAL_SPECIALIZED_TEMPLATE_PREFIX_UUID(AZ::EventHandler, "EventHandler", "{F85EFDA5-FBD0-4557-A3EF-9E077B41EA59}", AZ_TYPE_INFO_INTERNAL_TYPENAME_VARARGS);
}
#include <AzCore/EBus/Event.inl>
+12 -13
View File
@@ -15,14 +15,14 @@
namespace AZ
{
template <typename... Params>
Event<Params...>::Handler::Handler(std::nullptr_t)
EventHandler<Params...>::EventHandler(std::nullptr_t)
{
;
}
template <typename... Params>
Event<Params...>::Handler::Handler(Event<Params...>::Callback callback)
EventHandler<Params...>::EventHandler(Callback callback)
: m_callback(AZStd::move(callback))
{
;
@@ -30,7 +30,7 @@ namespace AZ
template <typename... Params>
Event<Params...>::Handler::Handler(const Handler& rhs)
EventHandler<Params...>::EventHandler(const EventHandler& rhs)
: m_callback(rhs.m_callback)
{
// Copy the callback function, then perform a Connect with the new event
@@ -42,7 +42,7 @@ namespace AZ
template <typename... Params>
Event<Params...>::Handler::Handler(Handler&& rhs)
EventHandler<Params...>::EventHandler(EventHandler&& rhs)
: m_event(rhs.m_event)
, m_index(rhs.m_index)
, m_callback(AZStd::move(rhs.m_callback))
@@ -56,14 +56,14 @@ namespace AZ
template <typename... Params>
Event<Params...>::Handler::~Handler()
EventHandler<Params...>::~EventHandler()
{
Disconnect();
}
template <typename... Params>
typename Event<Params...>::Handler& Event<Params...>::Handler::operator=(const Handler& rhs)
EventHandler<Params...>& EventHandler<Params...>::operator=(const EventHandler& rhs)
{
// Copy the callback function, then perform a Connect with the new event
if (this != &rhs)
@@ -81,7 +81,7 @@ namespace AZ
template <typename... Params>
typename Event<Params...>::Handler& Event<Params...>::Handler::operator=(Handler&& rhs)
EventHandler<Params...>& EventHandler<Params...>::operator=(EventHandler&& rhs)
{
if (this != &rhs)
{
@@ -96,12 +96,13 @@ namespace AZ
SwapEventHandlerPointers(rhs);
}
return *this;
}
template <typename... Params>
void Event<Params...>::Handler::Connect(Event<Params...>& event)
void EventHandler<Params...>::Connect(Event<Params...>& event)
{
// Cannot add an unbound event handle (no function callback) to an event, this is a programmer error
// We explicitly do not support binding the callback after the handler has been constructed so we can just reject the event handle here
@@ -119,7 +120,7 @@ namespace AZ
template <typename... Params>
void Event<Params...>::Handler::Disconnect()
void EventHandler<Params...>::Disconnect()
{
if (m_event)
{
@@ -127,16 +128,14 @@ namespace AZ
}
}
template <typename... Params>
bool Event<Params...>::Handler::IsConnected() const
bool EventHandler<Params...>::IsConnected() const
{
return m_event != nullptr;
}
template <typename... Params>
void Event<Params...>::Handler::SwapEventHandlerPointers([[maybe_unused]] const Handler& from)
void EventHandler<Params...>::SwapEventHandlerPointers([[maybe_unused]]const EventHandler& from)
{
// Find the pointer to the 'from' handler and point it to this handler
if (m_event)
@@ -101,7 +101,7 @@ namespace AZ
return AZ::TICK_ATTACHMENT;
}
ScheduledEventHandle* EventSchedulerSystemComponent::Add(ScheduledEvent* timedEvent, TimeMs durationMs)
ScheduledEventHandle* EventSchedulerSystemComponent::AddEvent(ScheduledEvent* timedEvent, TimeMs durationMs)
{
if (durationMs < TimeMs{ 0 })
{
@@ -118,6 +118,19 @@ namespace AZ
return timedEvent->m_handle;
}
void EventSchedulerSystemComponent::AddCallback(const AZStd::function<void()>& callback, const Name& eventName, TimeMs durationMs)
{
if (durationMs < TimeMs{ 0 })
{
durationMs = TimeMs{ 0 };
}
TimeMs currentMilliseconds = GetElapsedTimeMs();
ScheduledEvent* timedEvent = AllocateManagedEvent(TimeMs(currentMilliseconds + durationMs), durationMs, callback, eventName);
timedEvent->m_timeInserted = currentMilliseconds;
m_queue.push(timedEvent->m_handle);
}
AZStd::size_t EventSchedulerSystemComponent::GetHandleCount() const
{
return m_handles.size();
@@ -153,7 +166,26 @@ namespace AZ
m_handles.resize(m_handles.size() + 1);
result = &(m_handles.back());
}
*result = ScheduledEventHandle(executeTimeMs, durationTimeMs, scheduledEvent);
*result = ScheduledEventHandle(executeTimeMs, durationTimeMs, scheduledEvent, false);
return result;
}
ScheduledEvent* EventSchedulerSystemComponent::AllocateManagedEvent(TimeMs executeTimeMs, TimeMs durationTimeMs, const AZStd::function<void()>& callback, const Name& eventName)
{
ScheduledEvent* result = new ScheduledEvent(callback, eventName);
ScheduledEventHandle* handle = nullptr;
if (!m_freeHandles.empty())
{
handle = m_freeHandles.back();
m_freeHandles.pop_back();
}
else
{
m_handles.resize(m_handles.size() + 1);
handle = &(m_handles.back());
}
*handle = ScheduledEventHandle(executeTimeMs, durationTimeMs, result, true);
result->m_handle = handle;
return result;
}
@@ -42,6 +42,7 @@ namespace AZ
}
};
class Name;
class ScheduledEvent;
//! @class EventSchedulerSystemComponent
@@ -75,7 +76,8 @@ namespace AZ
//! IEventScheduler interface
//! @{
ScheduledEventHandle* Add(ScheduledEvent* scheduledEvent, TimeMs durationMs) override;
ScheduledEventHandle* AddEvent(ScheduledEvent* scheduledEvent, TimeMs durationMs) override;
void AddCallback(const AZStd::function<void()>& callback, const Name& eventName, TimeMs durationMs) override;
// @}
//! EventSchedulerSystemComponent stats
@@ -88,6 +90,8 @@ namespace AZ
private:
ScheduledEventHandle* AllocateHandle(TimeMs executeTimeMs, TimeMs durationTimeMs, ScheduledEvent* scheduledEvent);
// Allocates a single use event to capture the passed in callback. Event is cleaned up on completion.
ScheduledEvent* AllocateManagedEvent(TimeMs executeTimeMs, TimeMs durationTimeMs, const AZStd::function<void()>& callback, const Name& eventName);
void FreeHandle(ScheduledEventHandle* handle);
// Bind the DumpStats member function to the console as 'EventSchedulerSystemComponent.DumpStats'
@@ -20,6 +20,7 @@
namespace AZ
{
class Name;
class ScheduledEvent;
class ScheduledEventHandle;
@@ -36,12 +37,19 @@ namespace AZ
IEventScheduler() = default;
virtual ~IEventScheduler() = default;
//! Adds a scheduled event and intervalMs to run it.
//! Adds a scheduled event to run in durationMs.
//! Actual duration is not guaranteed but will not be less than the value provided.
//! @param scheduledEvent a scheduled event to add
//! @param durationMs an interval Ms to run the scheduled event
//! @param durationMs a millisecond interval to run the scheduled event
//! @return pointer to the handle for this scheduled event, IEventScheduler maintains ownership
virtual ScheduledEventHandle* Add(ScheduledEvent* scheduledEvent, TimeMs durationMs) = 0;
virtual ScheduledEventHandle* AddEvent(ScheduledEvent* scheduledEvent, TimeMs durationMs) = 0;
//! Schedules a callback to run in durationMs.
//! Actual duration is not guaranteed but will not be less than the value provided.
//! @param callback a callback to invoke after durationMs
//! @param eventName a text descriptor of the callback
//! @param durationMs a millisecond interval to run the scheduled callback
virtual void AddCallback(const AZStd::function<void()>& callback, const Name& eventName, TimeMs durationMs) = 0;
AZ_DISABLE_COPY_MOVE(IEventScheduler);
};
@@ -36,7 +36,7 @@ namespace AZ
RemoveFromQueue();
m_durationMs = durationMs;
m_autoRequeue = autoRequeue;
m_handle = eventScheduler->Add(this, durationMs);
m_handle = eventScheduler->AddEvent(this, durationMs);
}
}
@@ -59,7 +59,7 @@ namespace AZ
IEventScheduler* eventScheduler = Interface<IEventScheduler>::Get();
if (eventScheduler)
{
m_handle = eventScheduler->Add(this, m_durationMs);
m_handle = eventScheduler->AddEvent(this, m_durationMs);
}
}
@@ -16,10 +16,11 @@
namespace AZ
{
ScheduledEventHandle::ScheduledEventHandle(TimeMs executeTimeMs, TimeMs durationTimeMs, ScheduledEvent* scheduledEvent)
ScheduledEventHandle::ScheduledEventHandle(TimeMs executeTimeMs, TimeMs durationTimeMs, ScheduledEvent* scheduledEvent, bool isAutoDelete)
: m_executeTimeMs(executeTimeMs)
, m_durationMs(durationTimeMs)
, m_event(scheduledEvent)
, m_autoDelete(isAutoDelete)
{
;
}
@@ -48,6 +49,10 @@ namespace AZ
else // Not configured to auto-requeue, so remove the handle
{
m_event->ClearHandle();
if (m_autoDelete)
{
delete m_event;
}
m_event = nullptr;
}
}
@@ -31,7 +31,8 @@ namespace AZ
//! @param executeTimeMs an absolute time in ms at which point the scheduled event should trigger
//! @param durationTimeMs the interval time in ms used for prioritization as well as re-queueing
//! @param scheduledEvent a scheduled event to run
ScheduledEventHandle(TimeMs executeTimeMs, TimeMs durationTimeMs, ScheduledEvent* scheduledEvent);
//! @param autoDelete if the event handle will be automatically deleted after execution completes
ScheduledEventHandle(TimeMs executeTimeMs, TimeMs durationTimeMs, ScheduledEvent* scheduledEvent, bool isAutoDelete);
//! operator of comparing a scheduled event by execute time.
//! @param a_Rhs a scheduled event handle to compare
@@ -58,6 +59,7 @@ namespace AZ
TimeMs m_executeTimeMs = TimeMs{ 0 }; //< execution time of the scheduled event
TimeMs m_durationMs = TimeMs{ 0 }; //< interval time of the scheduled event
ScheduledEvent* m_event = nullptr; //< pointer to the scheduled event
bool m_autoDelete = false; //< if the handle manages the memory of its own event
};
}
+9 -2
View File
@@ -34,6 +34,10 @@ namespace AZ
int32_t numArgs = dc.GetNumArguments();
switch (numArgs)
{
case 0:
{
*thisPtr = Color(1.0f, 1.0f, 1.0f, 1.0f);
}
case 1:
{
if (dc.IsNumber(0))
@@ -42,9 +46,12 @@ namespace AZ
dc.ReadArg(0, number);
*thisPtr = Color(number);
}
else
else if (!(ConstructOnTypedArgument<Color>(*thisPtr, dc, 0)
|| ConstructOnTypedArgument<Vector4>(*thisPtr, dc, 0)
|| ConstructOnTypedArgument<Vector3>(*thisPtr, dc, 0)
|| ConstructOnTypedArgument<Vector2>(*thisPtr, dc, 0)))
{
dc.GetScriptContext()->Error(AZ::ScriptContext::ErrorType::Error, true, "When only providing 1 argument to Color(), it must be a number!");
dc.GetScriptContext()->Error(AZ::ScriptContext::ErrorType::Error, true, "When only providing 1 argument to Color(), it must be a number, Color, Vector4, Vector3, or Vector2");
}
} break;
case 3:
+7 -2
View File
@@ -12,6 +12,7 @@
#pragma once
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector4.h>
@@ -32,12 +33,16 @@ namespace AZ
Color() = default;
Color(const Vector4& v) { m_color = v; }
explicit Color(const Vector2& source);
explicit Color(const Vector3& source);
//! Constructs vector with all components set to the same specified value.
explicit Color(float rgba);
explicit Color(float r, float g, float b, float a);
Color(float r, float g, float b, float a);
explicit Color(u8 r, u8 g, u8 b, u8 a);
Color(u8 r, u8 g, u8 b, u8 a);
//! Creates a vector with all components set to zero, more efficient than calling Color(0.0f).
static Color CreateZero();
+11 -2
View File
@@ -13,20 +13,29 @@
namespace AZ
{
AZ_MATH_INLINE Color::Color(const Vector2& source)
{
m_color = Vector4(source);
}
AZ_MATH_INLINE Color::Color(const Vector3& source)
{
m_color = Vector4(source);
}
AZ_MATH_INLINE Color::Color(float rgba)
: m_color(rgba)
{
;
}
AZ_MATH_INLINE Color::Color(float r, float g, float b, float a)
: m_color(r, g, b, a)
{
;
}
AZ_MATH_INLINE Color::Color(u8 r, u8 g, u8 b, u8 a)
{
SetR8(r);
@@ -289,6 +289,10 @@ namespace AZ
->Attribute(AZ::Script::Attributes::MethodOverride, &Internal::GetSinCosMultipleReturn)
->Method<bool(double, double, double)>("IsClose", &AZ::IsClose, context.MakeDefaultValues(static_cast<double>(Constants::FloatEpsilon)))
->Method<float(float)>("Abs", &GetAbs)
->Method("Divide By Number", [](float lhs, float rhs) { return lhs / rhs; })
->Attribute(AZ::ScriptCanvasAttributes::ExplicitOverloadCrc, ExplicitOverloadInfo("Divide By Number (/)", "Math"))
->Attribute(AZ::ScriptCanvasAttributes::OperatorOverride, AZ::Script::Attributes::OperatorType::Div)
->Attribute(AZ::ScriptCanvasAttributes::OverloadArgumentGroup, AZ::OverloadArgumentGroupInfo({ "DivideGroup", "" }, { "DivideGroup" }))
;
// Uuid
@@ -301,6 +301,10 @@ namespace AZ
Attribute(Script::Attributes::Ignore, 0)-> // ignore for script since we already got the generic multiply above
Method<Matrix4x4(Matrix4x4::*)(const Matrix4x4&) const>("MultiplyMatrix4x4", &Matrix4x4::operator*)->
Attribute(Script::Attributes::Ignore, 0)-> // ignore for script since we already got the generic multiply above
Method<Matrix4x4(Matrix4x4::*)(const Matrix4x4&) const>("Add", &Matrix4x4::operator+)->
Attribute(Script::Attributes::Operator, Script::Attributes::OperatorType::Add)->
Method<Matrix4x4(Matrix4x4::*)(const Matrix4x4&) const>("Subtract", &Matrix4x4::operator-)->
Attribute(Script::Attributes::Operator, Script::Attributes::OperatorType::Sub)->
Method("Equal", &Matrix4x4::operator==)->
Attribute(Script::Attributes::Operator, Script::Attributes::OperatorType::Equal)->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
@@ -171,6 +171,12 @@ namespace AZ
void SetTranslation(const Vector3& v);
//! @}
Matrix4x4 operator+(const Matrix4x4& rhs) const;
Matrix4x4& operator+=(const Matrix4x4& rhs);
Matrix4x4 operator-(const Matrix4x4& rhs) const;
Matrix4x4& operator-=(const Matrix4x4& rhs);
Matrix4x4 operator*(const Matrix4x4& rhs) const;
Matrix4x4& operator*=(const Matrix4x4& rhs);
@@ -477,6 +477,37 @@ namespace AZ
}
AZ_MATH_INLINE Matrix4x4 Matrix4x4::operator+(const Matrix4x4& rhs) const
{
return Matrix4x4
( Simd::Vec4::Add(m_rows[0].GetSimdValue(), rhs.m_rows[0].GetSimdValue())
, Simd::Vec4::Add(m_rows[1].GetSimdValue(), rhs.m_rows[1].GetSimdValue())
, Simd::Vec4::Add(m_rows[2].GetSimdValue(), rhs.m_rows[2].GetSimdValue())
, Simd::Vec4::Add(m_rows[3].GetSimdValue(), rhs.m_rows[3].GetSimdValue()));
}
AZ_MATH_INLINE Matrix4x4 Matrix4x4::operator-(const Matrix4x4& rhs) const
{
return Matrix4x4
( Simd::Vec4::Sub(m_rows[0].GetSimdValue(), rhs.m_rows[0].GetSimdValue())
, Simd::Vec4::Sub(m_rows[1].GetSimdValue(), rhs.m_rows[1].GetSimdValue())
, Simd::Vec4::Sub(m_rows[2].GetSimdValue(), rhs.m_rows[2].GetSimdValue())
, Simd::Vec4::Sub(m_rows[3].GetSimdValue(), rhs.m_rows[3].GetSimdValue()));
}
AZ_MATH_INLINE Matrix4x4& Matrix4x4::operator+=(const Matrix4x4& rhs)
{
*this = *this + rhs;
return *this;
}
AZ_MATH_INLINE Matrix4x4& Matrix4x4::operator-=(const Matrix4x4& rhs)
{
*this = *this - rhs;
return *this;
}
AZ_MATH_INLINE Matrix4x4 Matrix4x4::operator*(const Matrix4x4& rhs) const
{
Matrix4x4 result;
@@ -70,7 +70,7 @@ namespace AZ
}
}
void PolygonPrism::OnChangeHeight() const
void PolygonPrism::OnChangeHeight()
{
if (m_onChangeHeightCallback)
{
@@ -78,10 +78,30 @@ namespace AZ
}
}
void PolygonPrism::SetNonUniformScale(const AZ::Vector3& nonUniformScale)
{
m_nonUniformScale = nonUniformScale;
OnChangeNonUniformScale();
}
void PolygonPrism::OnChangeNonUniformScale()
{
if (m_onChangeNonUniformScaleCallback)
{
m_onChangeNonUniformScaleCallback();
}
}
AZ::Vector3 PolygonPrism::GetNonUniformScale() const
{
return m_nonUniformScale;
}
void PolygonPrism::SetCallbacks(
const VoidFunction& onChangeElement,
const VoidFunction& onChangeContainer,
const VoidFunction& onChangeHeight)
const VoidFunction& onChangeHeight,
const VoidFunction& onChangeNonUniformScale)
{
m_vertexContainer.SetCallbacks(
[onChangeContainer](size_t) { onChangeContainer(); },
@@ -91,12 +111,14 @@ namespace AZ
onChangeContainer);
m_onChangeHeightCallback = onChangeHeight;
m_onChangeNonUniformScaleCallback = onChangeNonUniformScale;
}
void PolygonPrism::SetCallbacks(
const IndexFunction& onAddVertex, const IndexFunction& onRemoveVertex,
const IndexFunction& onUpdateVertex, const VoidFunction& onSetVertices,
const VoidFunction& onClearVertices, const VoidFunction& onChangeHeight)
const VoidFunction& onClearVertices, const VoidFunction& onChangeHeight,
const VoidFunction& onChangeNonUniformScale)
{
m_vertexContainer.SetCallbacks(
onAddVertex,
@@ -106,6 +128,7 @@ namespace AZ
onClearVertices);
m_onChangeHeightCallback = onChangeHeight;
m_onChangeNonUniformScaleCallback = onChangeNonUniformScale;
}
AZ_CLASS_ALLOCATOR_IMPL(PolygonPrism, SystemAllocator, 0)
@@ -13,6 +13,7 @@
#pragma once
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/VertexContainer.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
@@ -22,11 +23,9 @@ namespace AZ
class ReflectContext;
void PolygonPrismReflect(ReflectContext* context);
/**
* Formal Definition: A (right) polygonal prism is a 3-dimensional prism made from two translated polygons connected by rectangles. Parallelogram sides are not allowed.
* Here the representation is defined by one polygon (internally represented as a vertex container - list of vertices) and a height (extrusion) property.
* All vertices lie on the local plane Z = 0.
*/
//! Formal Definition: A (right) polygonal prism is a 3-dimensional prism made from two translated polygons connected by rectangles. Parallelogram sides are not allowed.
//! Here the representation is defined by one polygon (internally represented as a vertex container - list of vertices) and a height (extrusion) property.
//! All vertices lie on the local plane Z = 0.
class PolygonPrism
{
public:
@@ -36,46 +35,50 @@ namespace AZ
PolygonPrism() = default;
virtual ~PolygonPrism() = default;
/**
* Set the height of the polygon prism.
*/
//! Set the height of the polygon prism.
void SetHeight(float height);
/**
* Return the height of the polygon prism.
*/
//! Return the height of the polygon prism.
float GetHeight() const { return m_height; }
/**
* Override callbacks to be used when polygon prism changes/is modified (general).
*/
//! Set the non-uniform scale applied to the polygon prism.
void SetNonUniformScale(const AZ::Vector3& nonUniformScale);
//! Return the non-uniform scale applied to the polygon prism.
AZ::Vector3 GetNonUniformScale() const;
//! Override callbacks to be used when polygon prism changes/is modified (general).
void SetCallbacks(
const VoidFunction& onChangeElement,
const VoidFunction& onChangeContainer,
const VoidFunction& onChangeHeight);
const VoidFunction& onChangeHeight,
const VoidFunction& onChangeNonUniformScale);
/**
* Override callbacks to be used when spline changes/is modified (specific).
* (use if you need more fine grained control over modifications to the container)
*/
//! Override callbacks to be used when spline changes/is modified (specific).
//! (use if you need more fine grained control over modifications to the container)
void SetCallbacks(
const IndexFunction& onAddVertex, const IndexFunction& onRemoveVertex,
const IndexFunction& onUpdateVertex, const VoidFunction& onSetVertices,
const VoidFunction& onClearVertices, const VoidFunction& onChangeHeight);
const VoidFunction& onClearVertices, const VoidFunction& onChangeHeight,
const VoidFunction& onChangeNonUniformScale);
static void Reflect(ReflectContext* context);
VertexContainer<Vector2> m_vertexContainer; ///< Reference to underlying vertex data.
private:
VoidFunction m_onChangeHeightCallback = nullptr; ///< Callback for when height is changed.
float m_height = 1.0f; ///< Height of polygon prism (about local Z) - default to 1m.
VoidFunction m_onChangeHeightCallback = nullptr; //!< Callback for when height is changed.
VoidFunction m_onChangeNonUniformScaleCallback = nullptr; //!< Callback for when non-uniform scale is changed.
float m_height = 1.0f; //!< Height of polygon prism (about local Z) - default to 1m.
AZ::Vector3 m_nonUniformScale = AZ::Vector3::CreateOne(); //!< Allows non-uniform scale to be applied to the prism.
/**
* Internally used to call OnChangeCallback when height values are modified in the property grid.
*/
void OnChangeHeight() const;
//! Internally used to call OnChangeCallback when height values are modified in the property grid.
void OnChangeHeight();
//! Internally used to call OnChangeCallback when non-uniform scale values are modified.
void OnChangeNonUniformScale();
};
using PolygonPrismPtr = AZStd::shared_ptr<PolygonPrism>;
using ConstPolygonPrismPtr = AZStd::shared_ptr<const PolygonPrism>;
}
}
@@ -216,6 +216,7 @@ namespace AZ
Method("SetElement", &Quaternion::SetElement)->
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)->
Method("GetLength", &Quaternion::GetLength)->
Attribute(AZ::ScriptCanvasAttributes::ExplicitOverloadCrc, ExplicitOverloadInfo("Length", "Math"))->
Method("GetLengthSq", &Quaternion::GetLengthSq)->
Method("GetLengthReciprocal", &Quaternion::GetLengthReciprocal)->
Method("GetNormalized", &Quaternion::GetNormalized)->
@@ -240,6 +240,7 @@ namespace AZ
Method("SetElement", &Vector2::SetElement, { { {"Index", ""}, {"Value",""} } })->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
Method("GetLength", &Vector2::GetLength)->
Attribute(AZ::ScriptCanvasAttributes::ExplicitOverloadCrc, ExplicitOverloadInfo("Length", "Math"))->
Method("GetLengthSq", &Vector2::GetLengthSq)->
Method("SetLength", &Vector2::SetLength)->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
@@ -311,7 +312,10 @@ namespace AZ
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
Method("Madd", &Vector2::Madd)->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
Method("ConstructFromValues", &Internal::ConstructVector2)
Method("ConstructFromValues", &Internal::ConstructVector2)->
Method<Vector2(Vector2::*)(float) const>("DivideFloatExplicit", &Vector2::operator/)->
Attribute(AZ::ScriptCanvasAttributes::ExplicitOverloadCrc, ExplicitOverloadInfo("Divide By Number (/)", "Math"))->
Attribute(AZ::ScriptCanvasAttributes::OverloadArgumentGroup, AZ::OverloadArgumentGroupInfo({ "DivideGroup", "" }, { "DivideGroup" }))
;
}
}
@@ -256,6 +256,7 @@ namespace AZ
Method("SetElement", &Vector3::SetElement)->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
Method("GetLength", &Vector3::GetLength)->
Attribute(AZ::ScriptCanvasAttributes::ExplicitOverloadCrc, ExplicitOverloadInfo("Length", "Math"))->
Method("GetLengthSq", &Vector3::GetLengthSq)->
Method("GetLengthReciprocal", &Vector3::GetLengthReciprocal)->
Method("GetNormalized", &Vector3::GetNormalized)->
@@ -343,7 +344,10 @@ namespace AZ
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
Method("CreateZero", &Vector3::CreateZero)->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
Method("ConstructFromValues", &ScriptCanvas::ConstructVector3)
Method("ConstructFromValues", &ScriptCanvas::ConstructVector3)->
Method<Vector3(Vector3::*)(float) const>("DivideFloatExplicit", &Vector3::operator/)->
Attribute(AZ::ScriptCanvasAttributes::ExplicitOverloadCrc, ExplicitOverloadInfo("Divide By Number (/)", "Math"))->
Attribute(AZ::ScriptCanvasAttributes::OverloadArgumentGroup, AZ::OverloadArgumentGroupInfo({ "DivideGroup", "" }, { "DivideGroup" }))
;
}
}
@@ -266,6 +266,7 @@ namespace AZ
Method("SetElement", &Vector4::SetElement)->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
Method("GetLength", &Vector4::GetLength)->
Attribute(AZ::ScriptCanvasAttributes::ExplicitOverloadCrc, ExplicitOverloadInfo("Length", "Math"))->
Method("GetLengthSq", &Vector4::GetLengthSq)->
Method("GetLengthReciprocal", &Vector4::GetLengthReciprocal)->
Method("GetNormalized", &Vector4::GetNormalized)->
@@ -316,7 +317,10 @@ namespace AZ
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
Method("CreateZero", &Vector4::CreateZero)->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
Method("ConstructFromValues", &Internal::ConstructVector4)
Method("ConstructFromValues", &Internal::ConstructVector4)->
Method<Vector4(Vector4::*)(float) const>("DivideFloatExplicit", &Vector4::operator/)->
Attribute(AZ::ScriptCanvasAttributes::ExplicitOverloadCrc, ExplicitOverloadInfo("Divide By Number (/)", "Math"))->
Attribute(AZ::ScriptCanvasAttributes::OverloadArgumentGroup, AZ::OverloadArgumentGroupInfo({ "DivideGroup", "" }, { "DivideGroup" }))
;
}
}
@@ -1,23 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
/// @cond EXCLUDE_DOCS
/** @file
* @deprecated Use <AzCore/Slice/SliceBus.h>
*/
#pragma once
#include <AzCore/Slice/SliceBus.h>
/// @endcond
@@ -1,23 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
/// @cond EXCLUDE_DOCS
/** @file
* @deprecated Use <AzCore/Slice/SliceComponent.h>
*/
#pragma once
#include <AzCore/Slice/SliceComponent.h>
/// @endcond
@@ -22,6 +22,7 @@
#include <AzCore/std/string/tokenize.h>
#include <AzCore/RTTI/AzStdOnDemandPrettyName.inl>
#include <AzCore/RTTI/AzStdOnDemandReflectionLuaFunctions.inl>
#include <AzCore/EBus/Event.h>
// forward declare specialized types
namespace AZStd
@@ -361,6 +362,62 @@ namespace AZ
typename ContainerType::iterator m_end;
};
template<typename T>
using decay_array = AZStd::conditional_t<AZStd::is_array_v<AZStd::remove_reference_t<T>>, std::remove_extent_t<AZStd::remove_reference_t<T>>*, T&&>;
template<typename... T>
BehaviorObject CreateConnectedAZEventHandler(void* voidPtr, BehaviorFunction&& function)
{
auto behaviorForwardingFunction = [function](T... args)
{
AZStd::tuple<decay_array<T>...> lvalueWrapper(AZStd::forward<T>(args)...);
using BVPReserveArray = AZStd::array<AZ::BehaviorValueParameter, sizeof...(args)>;
auto MakeBVPArrayFunction = [](auto&&... element)
{
return BVPReserveArray{ {AZ::BehaviorValueParameter{&element}...} };
};
BVPReserveArray argsBVPs = AZStd::apply(MakeBVPArrayFunction, lvalueWrapper);
function(nullptr, argsBVPs.data(), sizeof...(T));
};
auto result = aznew AZ::EventHandler<T...>(AZStd::move(behaviorForwardingFunction));
auto eventPtr = reinterpret_cast<AZ::Event<T...>*>(voidPtr);
result->Connect(*eventPtr);
return { reinterpret_cast<void*>(result), azrtti_typeid<AZ::EventHandler<T...>>() };
}
template<typename... T>
struct OnDemandReflection<AZ::Event<T...>>
{
template<typename U>
static AZ::BehaviorParameter CreateBehaviorEventParameter()
{
AZ::BehaviorParameter param;
AZ::Internal::SetParametersStripped<U>(&param, nullptr);
return param;
}
static void Reflect(ReflectContext* context)
{
if (auto behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
AZ::EventHandlerCreationFunctionHolder createHandlerHolder;
createHandlerHolder.m_function = &CreateConnectedAZEventHandler<T...>;
AZStd::vector<AZ::BehaviorParameter> eventParamsTypes{ AZStd::initializer_list<AZ::BehaviorParameter>{
CreateBehaviorEventParameter<decay_array<T>>()... } };
behaviorContext->Class<AZ::Event<T...>>()
->Attribute(AZ::Script::Attributes::EventHandlerCreationFunction, createHandlerHolder)
->Attribute(AZ::Script::Attributes::EventParameterTypes, eventParamsTypes)
->Method("HasHandlerConnected", &AZ::Event<T...>::HasHandlerConnected)
;
behaviorContext->Class<AZ::EventHandler<T...>>()
->Method("Disconnect", &AZ::EventHandler<T...>::Disconnect)
;
}
}
};
/// OnDemand reflection for AZStd::vector
template<class T, class A>
struct OnDemandReflection< AZStd::vector<T, A> >
@@ -908,6 +965,11 @@ namespace AZ
emptyBranchInfo.m_trueToolTip = "The container is empty";
emptyBranchInfo.m_falseToolTip = "The container is not empty";
auto ContainsTransparent = [](const ContainerType& containerType, typename ContainerType::key_type& key)->bool
{
return containerType.contains(key);
};
ExplicitOverloadInfo explicitOverloadInfo;
behaviorContext->Class<ContainerType>()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
@@ -917,7 +979,7 @@ namespace AZ
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::ScriptOwn)
->Method(k_accessElementName, &At)
->Attribute(AZ::Script::Attributes::TreatAsMemberFunction, AZ::AttributeIsValid::IfPresent)
->Attribute(AZ::ScriptCanvasAttributes::CheckedOperation, CheckedOperationInfo("Contains", {}, "Out", "Key Not Found"))
->Attribute(AZ::ScriptCanvasAttributes::CheckedOperation, CheckedOperationInfo("contains", {}, "Out", "Key Not Found"))
->Attribute(AZ::ScriptCanvasAttributes::ExplicitOverloadCrc, ExplicitOverloadInfo("Get Element", "Containers"))
->Method("BucketCount", static_cast<typename ContainerType::size_type(ContainerType::*)() const>(&ContainerType::bucket_count))
->Method("Empty", static_cast<bool(ContainerType::*)() const>(&ContainerType::empty), { { { "Container", "The container to check if it is empty", nullptr, {} } } })
@@ -933,8 +995,9 @@ namespace AZ
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Method("GetKeys", &GetKeys)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Method("Contains", [](ContainerType& map, Key& key)->bool { return map.contains(key); }, { { { "Key", "The key to check for", nullptr, {} } } })
->Method("contains", ContainsTransparent, { { { "Key", "The key to check for", nullptr, {} } } })
->Attribute(AZ::ScriptCanvasAttributes::ExplicitOverloadCrc, ExplicitOverloadInfo("Has Key", "Containers"))
->Attribute(AZ::Script::Attributes::TreatAsMemberFunction, AZ::AttributeIsValid::IfPresent)
->Method("Insert", &Insert, { { {}, { "Index", "The index at which to insert the value", nullptr, {} }, {} } })
->Attribute(AZ::Script::Attributes::TreatAsMemberFunction, AZ::AttributeIsValid::IfPresent)
->Attribute(AZ::ScriptCanvasAttributes::ExplicitOverloadCrc, ExplicitOverloadInfo("Insert", "Containers"))
@@ -1011,6 +1074,11 @@ namespace AZ
{
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
auto ContainsTransparent = [](const ContainerType& containerType, typename ContainerType::key_type& key)->bool
{
return containerType.contains(key);
};
behaviorContext->Class<ContainerType>()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::ScriptCanvasAttributes::PrettyName, ScriptCanvasOnDemandReflection::OnDemandPrettyName<ContainerType>::Get(*behaviorContext))
@@ -1020,7 +1088,8 @@ namespace AZ
->Method("BucketCount", static_cast<typename ContainerType::size_type(ContainerType::*)() const>(&ContainerType::bucket_count))
->Method("Erase", &Erase)
->Method("Empty", [](ContainerType& thisSet)->bool { return thisSet.empty(); })
->Method("contains", [](ContainerType& thisSet, Key& key)->bool { return thisSet.find(key) != thisSet.end(); })
->Method("contains", ContainsTransparent)
->Attribute(AZ::Script::Attributes::TreatAsMemberFunction, AZ::AttributeIsValid::IfPresent)
->Method("Insert", &Insert)
->Method(k_sizeName, [](ContainerType* thisPtr) { return aznumeric_cast<int>(thisPtr->size()); })
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Length)
@@ -16,6 +16,98 @@
namespace AZ
{
bool MethodReturnsAzEventByReferenceOrPointer(const AZ::BehaviorMethod& method)
{
const AZ::BehaviorParameter* resultParameter = method.GetResult();
if (resultParameter == nullptr)
{
return false;
}
// The return parameter must have AZ Rtti to in order for it be an AZ::Event parameter
AZ::IRttiHelper* rttiHelper = resultParameter->m_azRtti;
if (!rttiHelper || rttiHelper->GetGenericTypeId() != azrtti_typeid<AZ::Event>())
{
return false;
}
constexpr auto PointerValueTrait = AZ::BehaviorParameter::Traits::TR_REFERENCE | AZ::BehaviorParameter::Traits::TR_POINTER;
return (resultParameter->m_traits & PointerValueTrait) != AZ::BehaviorParameter::Traits::TR_NONE;
}
bool ValidateAzEventDescription(const BehaviorContext& context, const AZ::BehaviorMethod& method)
{
const AZ::BehaviorParameter* resultParameter = method.GetResult();
if (resultParameter == nullptr)
{
return false;
}
// The return parameter must have AZ Rtti to in order for it be an AZ::Event& or AZ::Event* parameter
AZ::IRttiHelper* rttiHelper = resultParameter->m_azRtti;
if (!rttiHelper || rttiHelper->GetGenericTypeId() != azrtti_typeid<AZ::Event>())
{
return false;
}
constexpr auto PointerValueTrait = AZ::BehaviorParameter::Traits::TR_REFERENCE | AZ::BehaviorParameter::Traits::TR_POINTER;
const auto parameterTraits = static_cast<AZ::BehaviorParameter::Traits>(resultParameter->m_traits) & PointerValueTrait;
if (parameterTraits == AZ::BehaviorParameter::Traits::TR_NONE)
{
return false;
}
bool azEventDescValid = true;
AZ::Attribute* azEventDescAttribute = AZ::FindAttribute(AZ::Script::Attributes::AzEventDescription, method.m_attributes);
AZ::AttributeReader azEventDescAttributeReader(nullptr, azEventDescAttribute);
AZ::BehaviorAzEventDescription behaviorAzEventDesc;
if (!azEventDescAttributeReader.Read<decltype(behaviorAzEventDesc)>(behaviorAzEventDesc))
{
AZ_Error("BehaviorContext", false, "Unable to read AzEventDescription attribute of method %s"
" that returns an AZ::Event", method.m_name.c_str());
return false;
}
if (behaviorAzEventDesc.m_eventName.empty())
{
AZ_Error("BehaviorContext", false, "AzEventDescription attribute on method %s"
" has an empty event name", method.m_name.c_str());
azEventDescValid = false;
}
auto azEventClassIter = context.m_typeToClassMap.find(rttiHelper->GetTypeId());
if (azEventClassIter != context.m_typeToClassMap.end() && azEventClassIter->second != nullptr)
{
AZ::BehaviorClass* azEventClass = azEventClassIter->second;
AZ::Attribute* eventParameterTypesAttribute = AZ::FindAttribute(AZ::Script::Attributes::EventParameterTypes,
azEventClass->m_attributes);
AZStd::vector<AZ::BehaviorParameter> eventParameterTypes;
if (AZ::AttributeReader(nullptr, eventParameterTypesAttribute).Read<decltype(eventParameterTypes)>(eventParameterTypes))
{
if (eventParameterTypes.size() != behaviorAzEventDesc.m_parameterNames.size())
{
AZ_Error("BehaviorContext", false, "AzEventDescription only contains names for %zu parameters,"
" while the AZ::Event(%s) accepts %zu parameters", behaviorAzEventDesc.m_parameterNames.size(),
behaviorAzEventDesc.m_eventName.c_str(), eventParameterTypes.size());
azEventDescValid = false;
}
size_t parameterIndex = 0;
for (AZStd::string_view parameterName : behaviorAzEventDesc.m_parameterNames)
{
if (parameterName.empty())
{
AZ_Error("BehaviorContext", false, "AzEventDescription parameter %zu contains an empty name parameter"
" for AZ::Event(%s)", parameterIndex, behaviorAzEventDesc.m_eventName.c_str());
azEventDescValid = false;
}
++parameterIndex;
}
}
}
return azEventDescValid;
}
//=========================================================================
// BehaviorMethod
//=========================================================================
@@ -233,6 +325,10 @@ namespace AZ
if (m_method)
{
if (MethodReturnsAzEventByReferenceOrPointer(*m_method))
{
ValidateAzEventDescription(*Base::m_context, *m_method);
}
BehaviorContextBus::Event(m_context, &BehaviorContextBus::Events::OnAddGlobalMethod, m_name, m_method);
}
}
@@ -268,6 +364,12 @@ namespace AZ
if (m_prop)
{
// Only the property getter needs to be validated to determine if it returns an AZ::Event
// and have the AzEventDescription attribute attached to that event
if (m_prop->m_getter && MethodReturnsAzEventByReferenceOrPointer(*m_prop->m_getter))
{
ValidateAzEventDescription(*Base::m_context, *m_prop->m_getter);
}
BehaviorContextBus::Event(m_context, &BehaviorContextBus::Events::OnAddGlobalProperty, m_prop->m_name.c_str(), m_prop);
}
}
@@ -79,6 +79,7 @@ namespace AZ
*/
struct BehaviorParameter
{
AZ_TYPE_INFO(BehaviorParameter, "{BD7B664E-5B8C-4B51-84F3-DE89B271E075}")
/// Temporary POD buffer when we convert parameters on the stack.
typedef AZStd::static_buffer_allocator<32, 32> TempValueParameterAllocator;
@@ -243,6 +244,27 @@ namespace AZ
virtual BehaviorDefaultValuePtr GetDefaultValue(size_t i) const = 0;
};
/**
* Stores the name of an AZ::Event<Params...> and names for each of it's parameters
* For use in scripting to annotate functions and nodes with user-friendly names
*/
struct BehaviorAzEventDescription
{
AZ_TYPE_INFO(BehaviorAzEventDescription, "{B5D95E87-FA17-41C7-AC90-7258A520FE82}");
AZStd::string m_eventName;
AZStd::vector<AZStd::string> m_parameterNames;
};
//! Checks if the supplied BehaviorMethod returns AZ::Event by either pointer or reference
bool MethodReturnsAzEventByReferenceOrPointer(const AZ::BehaviorMethod& method);
//! Validates that a method that returns an AZ::Event fulfills the following conditions.
//! 1. It has an AzEventDescription that stores a BehaviorAzEventDescription instance
//! 2. The number of parameters that the method accepts, matches the number of elements
//! in the parameter names array
//! 3. Neither the AZ::Event name nor any of it's parameters are an empty string
bool ValidateAzEventDescription(const AZ::BehaviorContext& context, const AZ::BehaviorMethod& method);
/**
* Use behavior method to get type information and invoke reflected methods.
*/
@@ -393,7 +415,10 @@ namespace AZStd
template<>
struct hash<AZ::ExplicitOverloadInfo>
{
AZ_INLINE size_t operator() (const AZ::ExplicitOverloadInfo& info) const
using argument_type = AZ::ExplicitOverloadInfo;
using result_type = size_t;
AZ_INLINE result_type operator() (const argument_type& info) const
{
size_t h = 0;
hash_combine(h, info.m_name);
@@ -404,7 +429,10 @@ namespace AZStd
template<>
struct hash<AZ::CheckedOperationInfo>
{
AZ_INLINE size_t operator() (const AZ::CheckedOperationInfo& info) const
using argument_type = AZ::CheckedOperationInfo;
using result_type = size_t;
AZ_INLINE result_type operator() (const argument_type& info) const
{
size_t h = 0;
hash_combine(h, info.m_safetyCheckName);
@@ -415,6 +443,18 @@ namespace AZStd
namespace AZ
{
// AZ::Event support
using BehaviorFunction = AZStd::function<void(BehaviorValueParameter* result, BehaviorValueParameter* arguments, int numArguments)>;
using EventHandlerCreationFunction = AZStd::function<BehaviorObject(void* , BehaviorFunction&&)>;
struct EventHandlerCreationFunctionHolder
{
AZ_TYPE_INFO(EventHandlerCreationFunctionHolder, "{40F7C5D8-8DA0-4979-BC8C-0A52EDA80633}");
AZ_CLASS_ALLOCATOR(EventHandlerCreationFunctionHolder, AZ::SystemAllocator, 0);
EventHandlerCreationFunction m_function;
};
namespace Internal
{
const AZ::TypeId& GetUnderlyingTypeId(const IRttiHelper& enumRttiHelper);
@@ -1695,17 +1735,6 @@ namespace AZ
BehaviorEBus* m_ebus;
};
/// @cond EXCLUDE_DOCS
using GlobalMethodInfo = GlobalMethodBuilder; ///< @deprecated Use BehaviorContext::GlobalMethodBuilder
using GlobalPropertyInfo = GlobalPropertyBuilder; ///< @deprecated Use BehaviorContext::GlobalPropertyBuilder
template<class C>
using ClassReflection = ClassBuilder<C>; ///< @deprecated Use BehaviorContext::ClassBuilder
template<typename Bus>
using EBusReflection = EBusBuilder<Bus>; ///< @deprecated Use BehaviorContext::EBusBuilder
/// @endcond
BehaviorContext();
~BehaviorContext();
@@ -1779,9 +1808,6 @@ namespace AZ
return uuid == GetVoidTypeId();
}
// TODO: This is only for searching by string, do we even need that?
//ClassBuilder< OpenNamespace(const char* name);
//ClassBuilder<T> CloneNamespace();
AZStd::unordered_map<AZStd::string, BehaviorMethod*> m_methods; // TODO: make it a set and use the name inside method
AZStd::unordered_map<AZStd::string, BehaviorProperty*> m_properties; // TODO: make it a set and use the name inside property
@@ -2313,95 +2339,68 @@ namespace AZ
//////////////////////////////////////////////////////////////////////////
template<class T>
struct SetResult
{
static bool Set(BehaviorValueParameter& param, T& result, bool IsValueCopy)
// MSVC does not allow an incomplete type to be used in a compiler intrinsic which is the reason why
// std::is_assignable_v is not being used here
// For some reason the Script.cpp test validates that an incomplete type can be used with the SetResult struct
template<typename T, typename U, typename = void>
static constexpr bool IsCopyAssignable = false;
template<typename T, typename U>
static constexpr bool IsCopyAssignable<T, U, AZStd::void_t<decltype(AZStd::declval<T>() = AZStd::declval<U>())>> = true;
template<class T>
static bool Set(BehaviorValueParameter& param, T&& result, bool IsValueCopy)
{
using Type = AZStd::decay_t<T>;
if (param.m_traits & BehaviorParameter::TR_POINTER)
{
*reinterpret_cast<void**>(param.m_value) = (Type*)&result;
if constexpr (AZStd::is_pointer_v<Type>)
{
using ValueType = AZStd::remove_cvref_t<AZStd::remove_pointer_t<Type>>;
*reinterpret_cast<void**>(param.m_value) = const_cast<ValueType*>(result);
}
else
{
*reinterpret_cast<void**>(param.m_value) = &const_cast<Type&>(result);
}
return true;
}
else if (param.m_traits & BehaviorParameter::TR_REFERENCE)
{
param.m_value = (Type*)&result;
if constexpr (AZStd::is_pointer_v<Type>)
{
using ValueType = AZStd::remove_cvref_t<AZStd::remove_pointer_t<Type>>;
param.m_value = const_cast<ValueType*>(result);
}
else
{
param.m_value = &const_cast<Type&>(result);
}
return true;
}
else if (IsValueCopy)
{
// value copy
*reinterpret_cast<Type*>(param.m_value) = result;
return true;
}
return false;
}
};
template<class T>
struct SetResult<T*>
{
using Type = AZStd::decay_t<T>;
static bool ValueCopy(BehaviorValueParameter& param, T* result, const AZStd::true_type& /*AZStd::is_copy_constructible */)
{
new(param.m_value) Type(*result);
return true;
}
static bool ValueCopy(BehaviorValueParameter&, T* , const AZStd::false_type& /*AZStd::is_copy_constructible */)
{
return false;
}
static bool Set(BehaviorValueParameter& param, T* result, bool IsValueCopy)
{
if (param.m_traits & BehaviorParameter::TR_POINTER)
{
*reinterpret_cast<void**>(param.m_value) = (Type*)result;
return true;
}
else if (param.m_traits & BehaviorParameter::TR_REFERENCE)
{
param.m_value = (Type*)result;
return true;
}
else if (IsValueCopy)
{
// we need AZStd::is_complete so we can work with incomplete types, then we can enable the code below
return false;
//return ValueCopy(param, result, typename AZStd::conditional<AZStd::is_copy_constructible<Type>::value && !AZStd::is_abstract<Type>::value, AZStd::true_type, AZStd::false_type>::type());
}
return false;
}
};
template<class T>
struct SetResult<T*&> : public SetResult<T*>
{
};
template<class T>
struct SetResult<T&>
{
static bool Set(BehaviorValueParameter& param, T& result, bool IsValueCopy)
{
using Type = AZStd::decay_t<T>;
if (param.m_traits & BehaviorParameter::TR_POINTER)
{
*reinterpret_cast<void**>(param.m_value) = (Type*)&result;
return true;
}
else if (param.m_traits & BehaviorParameter::TR_REFERENCE)
{
param.m_value = (Type*)&result;
return true;
}
else if (IsValueCopy)
{
*reinterpret_cast<Type*>(param.m_value) = result;
if constexpr (AZStd::is_pointer_v<Type>)
{
using ValueType = AZStd::remove_cvref_t<AZStd::remove_pointer_t<Type>>;
if constexpr (IsCopyAssignable<ValueType&, AZStd::add_lvalue_reference_t<AZStd::remove_pointer_t<T>>>)
{
// copy if result is non-nullptr
if (result != nullptr)
{
*reinterpret_cast<ValueType*>(param.m_value) = *result;
}
}
}
else
{
// value copy
if constexpr (IsCopyAssignable<Type&, T>)
{
*reinterpret_cast<Type*>(param.m_value) = AZStd::forward<T>(result);
}
}
return true;
}
return false;
@@ -2435,7 +2434,7 @@ namespace AZ
if (m_typeId == typeId)
{
isResult = SetResult<T>::Set(*this, result, true);
isResult = SetResult::Set(*this, AZStd::forward<T>(result), true);
}
else if (GetRttiHelper<Type>())
{
@@ -2450,7 +2449,7 @@ namespace AZ
else if (m_typeId.IsNull()) // if nullptr we can accept any type, by pointer or reference
{
m_typeId = typeId;
isResult = SetResult<T>::Set(*this, result, false);
isResult = SetResult::Set(*this, AZStd::forward<T>(result), false);
}
if (isResult && m_onAssignedResult)
@@ -2519,8 +2518,15 @@ namespace AZ
template<class Getter>
bool BehaviorProperty::SetGetter(Getter getter, BehaviorClass* currentClass, BehaviorContext* context, const AZStd::false_type&)
{
typedef AZ::Internal::BehaviorMethodImpl<typename AZStd::RemoveFunctionConst<typename AZStd::remove_pointer<Getter>::type>::type> GetterType;
m_getter = aznew GetterType(getter, context, AZStd::string::format("%s::%s::Getter", currentClass ? currentClass->m_name.c_str() : "", m_name.c_str()));
using GetterType = AZ::Internal::BehaviorMethodImpl<typename AZStd::RemoveFunctionConst<AZStd::remove_pointer_t<Getter>>::type>;
AZStd::string getterPropertyName = currentClass ? currentClass->m_name : AZStd::string{};
if (!getterPropertyName.empty())
{
getterPropertyName += "::";
}
getterPropertyName += m_name;
getterPropertyName += "::Getter";
m_getter = aznew GetterType(getter, context, getterPropertyName);
if (AZStd::is_class<typename GetterType::ClassType>::value)
{
@@ -2590,8 +2596,15 @@ namespace AZ
template<class Setter>
bool BehaviorProperty::SetSetter(Setter setter, BehaviorClass* currentClass, BehaviorContext* context, const AZStd::false_type&)
{
typedef AZ::Internal::BehaviorMethodImpl<typename AZStd::RemoveFunctionConst<typename AZStd::remove_pointer<Setter>::type>::type> SetterType;
m_setter = aznew SetterType(setter, context, AZStd::string::format("%s::%s::Setter", currentClass ? currentClass->m_name.c_str() : "", m_name.c_str()));
using SetterType = AZ::Internal::BehaviorMethodImpl<typename AZStd::RemoveFunctionConst<AZStd::remove_pointer_t<Setter>>::type>;
AZStd::string setterPropertyName = currentClass ? currentClass->m_name : AZStd::string{};
if (!setterPropertyName.empty())
{
setterPropertyName += "::";
}
setterPropertyName += m_name;
setterPropertyName += "::Setter";
m_setter = aznew SetterType(setter, context, setterPropertyName);
if (AZStd::is_class<typename SetterType::ClassType>::value)
{
AZ_Assert(currentClass, "We should declare class property with in the class!");
@@ -2998,6 +3011,19 @@ namespace AZ
for (auto method : m_class->m_methods)
{
m_class->PostProcessMethod(Base::m_context, *method.second);
if (MethodReturnsAzEventByReferenceOrPointer(*method.second))
{
ValidateAzEventDescription(*Base::m_context, *method.second);
}
}
// Validate the AzEvent description of the class property getter's
for (auto&& [propertyName, propertyInst]: m_class->m_properties)
{
if (propertyInst->m_getter && MethodReturnsAzEventByReferenceOrPointer(*propertyInst->m_getter))
{
ValidateAzEventDescription(*Base::m_context, *propertyInst->m_getter);
}
}
BehaviorContextBus::Event(Base::m_context, &BehaviorContextBus::Events::OnAddClass, m_class->m_name.c_str(), m_class);
@@ -3307,8 +3333,7 @@ namespace AZ
BehaviorContext::ClassBuilder<C>* BehaviorContext::ClassBuilder<C>::Enum(const char* name)
{
Property(name, []() { return Value; }, nullptr);
BehaviorContext::ClassBuilder<C>::Attribute(AZ::Script::Attributes::ClassConstantValue, true);
ClassBuilder::Attribute(AZ::Script::Attributes::ClassConstantValue, true);
return this;
}
@@ -3448,6 +3473,13 @@ namespace AZ
if (!Base::m_context->IsRemovingReflection())
{
for (auto&& [eventName, eventSender] : m_ebus->m_events)
{
if (MethodReturnsAzEventByReferenceOrPointer(*eventSender.m_broadcast))
{
ValidateAzEventDescription(*Base::m_context, *eventSender.m_broadcast);
}
}
BehaviorContextBus::Event(Base::m_context, &BehaviorContextBus::Events::OnAddEBus, m_ebus->m_name.c_str(), m_ebus);
}
}
@@ -75,4 +75,44 @@ namespace AZ
/// returns true iff a and b have the same type and traits
bool TypeCompare(const BehaviorParameter& a, const BehaviorParameter& b);
// RAII class which scopes the creation and destruction of a BehaviorEBusHandler
// contained within the supplied BehaviorEBus class
struct ScopedBehaviorEBusHandler
{
ScopedBehaviorEBusHandler(const AZ::BehaviorEBus& behaviorEbus)
: m_behaviorEbus{ behaviorEbus }
{
if (m_behaviorEbus.m_createHandler)
{
m_behaviorEbus.m_createHandler->InvokeResult(m_handler);
}
}
~ScopedBehaviorEBusHandler()
{
if (m_handler && m_behaviorEbus.m_destroyHandler)
{
m_behaviorEbus.m_destroyHandler->Invoke(m_handler);
}
}
explicit operator bool() const
{
return m_handler;
}
AZ::BehaviorEBusHandler* operator->() const
{
return m_handler;
}
AZ::BehaviorEBusHandler& operator*() const
{
return *m_handler;
}
private:
const AZ::BehaviorEBus& m_behaviorEbus;
AZ::BehaviorEBusHandler* m_handler{};
};
}
@@ -99,6 +99,159 @@ namespace AzLsvInternal
namespace AZ
{
struct ExposedLambda
{
AZ_TYPE_INFO(ExposedLambda, "{B702DB0B-516B-4807-8007-DC50A5CE180A}");
AZ_CLASS_ALLOCATOR(ExposedLambda, AZ::SystemAllocator, 0);
// assumes a lambda is at the top of the stack and will pop it
ExposedLambda(lua_State* lua)
: m_lambdaRegistryIndex(luaL_ref_Checked(lua))
, m_lua(lua)
{
lua_pushinteger(lua, 1);
m_refCountRegistryIndex = luaL_ref_Checked(lua);
}
ExposedLambda(ExposedLambda&& source) noexcept
{
*this = AZStd::move(source);
}
ExposedLambda(const ExposedLambda& source)
{
*this = source;
}
~ExposedLambda()
{
if (m_lua && DecrementRefCount() == 0)
{
luaL_unref(m_lua, LUA_REGISTRYINDEX, m_lambdaRegistryIndex);
luaL_unref(m_lua, LUA_REGISTRYINDEX, m_refCountRegistryIndex);
}
}
ExposedLambda& operator=(const ExposedLambda& source)
{
if (this != &source)
{
m_lambdaRegistryIndex = source.m_lambdaRegistryIndex;
m_refCountRegistryIndex = source.m_refCountRegistryIndex;
m_lua = source.m_lua;
IncrementRefCount();
}
return *this;
}
ExposedLambda& operator=(ExposedLambda&& source) noexcept
{
if (this != &source)
{
m_lambdaRegistryIndex = source.m_lambdaRegistryIndex;
m_refCountRegistryIndex = source.m_refCountRegistryIndex;
source.m_lambdaRegistryIndex = source.m_refCountRegistryIndex = LUA_NOREF;
m_lua = source.m_lua;
source.m_lua = nullptr;
}
return *this;
}
void operator()([[maybe_unused]] AZ::BehaviorValueParameter* resultBVP, AZ::BehaviorValueParameter* argsBVPs, int numArguments)
{
auto behaviorContext = AZ::ScriptContext::FromNativeContext(m_lua)->GetBoundContext();
// Lua:
lua_rawgeti(m_lua, LUA_REGISTRYINDEX, m_lambdaRegistryIndex);
// Lua: lambda
for (int i = 0; i < numArguments; ++i)
{
ExposedLambda::StackPush(m_lua, behaviorContext, argsBVPs[i]);
}
// Lua: lambda, args...
Internal::LuaSafeCall(m_lua, numArguments, 0);
}
// \note Do not use, these are here for compiler compatibility only
ExposedLambda()
: m_lua(nullptr)
, m_lambdaRegistryIndex(LUA_NOREF)
, m_refCountRegistryIndex(LUA_NOREF)
{}
private:
static int luaL_ref_Checked(lua_State* lua)
{
AZ_Assert(lua, "null lua_State");
int ref = luaL_ref(lua, LUA_REGISTRYINDEX);
AZ_Assert(ref != LUA_NOREF && ref != LUA_REFNIL, "invalid lambdaRegistryIndex");
return ref;
}
template<typename T>
static T* GetAs(AZ::BehaviorValueParameter& argument)
{
return argument.m_typeId == azrtti_typeid<T>()
? reinterpret_cast<T*>(argument.GetValueAddress())
: nullptr;
}
static void StackPush(lua_State* lua, AZ::BehaviorContext* context, AZ::BehaviorValueParameter& argument)
{
if (auto cStringPtr = GetAs<const char*>(argument))
{
auto realValue = reinterpret_cast<const char*>(cStringPtr);
lua_pushstring(lua, realValue);
}
else if (auto stringPtr = GetAs<AZStd::string>(argument))
{
lua_pushlstring(lua, stringPtr->data(), stringPtr->size());
}
else if (auto viewPtr = GetAs<AZStd::string_view>(argument))
{
lua_pushlstring(lua, viewPtr->data(), viewPtr->size());
}
else
{
AZ::StackPush(lua, context, argument);
}
}
int m_lambdaRegistryIndex;
int m_refCountRegistryIndex;
lua_State* m_lua;
int AddRefCount(int value)
{
AZ_Assert(value == 1 || value == -1, "ModRefCount is only for incrementing or decrementing on copy or destruction of ExposedLambda")
lua_rawgeti(m_lua, LUA_REGISTRYINDEX, m_refCountRegistryIndex);
// Lua: refCount-old
const int refCount = Internal::azlua_tointeger(m_lua, -1) + value;
lua_pushinteger(m_lua, m_refCountRegistryIndex);
// Lua: refCount-old, registry index
lua_pushinteger(m_lua, refCount);
// Lua: refCount-old, registry index, refCount-new
lua_rawset(m_lua, LUA_REGISTRYINDEX);
// Lua: refCount-old
lua_pop(m_lua, 1);
// Lua:
return refCount;
}
int DecrementRefCount()
{
return AddRefCount(-1);
}
void IncrementRefCount()
{
AddRefCount(1);
}
};
constexpr const char* StorageTypeToString(Script::Attributes::StorageType storageType)
{
switch (storageType)
@@ -4942,6 +5095,11 @@ LUA_API const Node* lua_getDummyNode()
//lua_rawseti(l, -2, AZ_LUA_CLASS_METATABLE_STORAGE_CREATOR_INDEX);
BindClassMethodAndProperties(behaviorClass);
if (AZ::Attribute* eventHandlerCreationFunctionAttribute = FindAttribute(AZ::Script::Attributes::EventHandlerCreationFunction, behaviorClass->m_attributes))
{
BindEventSupport();
}
if (storageType != Script::Attributes::StorageType::Value)
{
@@ -5194,6 +5352,50 @@ LUA_API const Node* lua_getDummyNode()
Internal::azlua_setglobal(m_lua, ValidateName(ebusName));
}
static int ConnectToExposedEvent(lua_State* lua)
{
if (!(lua_isuserdata(lua, -2) && !lua_islightuserdata(lua, -2)))
{
ScriptContext::FromNativeContext(lua)->Error(ScriptContext::ErrorType::Error, true, "1st argument to ConnectToExposedEvent is not userdata");
return 0;
}
if (!lua_isfunction(lua, -1))
{
ScriptContext::FromNativeContext(lua)->Error(ScriptContext::ErrorType::Error, true, "2nd argument to ConnectToExposedEvent is not a function (lambda need to get around atypically routed arguments)");
return 0;
}
auto userData = reinterpret_cast<AZ::LuaUserData*>(lua_touserdata(lua, -2));
AZ_Assert(userData && userData->magicData == AZ_CRC_CE("AZLuaUserData"), "1st argument is not user AZ supported userdata");
AZ::Attribute* eventHandlerCreationFunctionAttribute = FindAttribute(AZ::Script::Attributes::EventHandlerCreationFunction, userData->behaviorClass->m_attributes);
AZ_Assert(eventHandlerCreationFunctionAttribute, "failure to expose AZ::Event type in class reflected to Lua");
AZ::AttributeReader attributeReader(nullptr, eventHandlerCreationFunctionAttribute);
AZ::EventHandlerCreationFunctionHolder holder;
attributeReader.Read<EventHandlerCreationFunctionHolder>(holder);
// Lua: ExposedEvent, lambda
lua_pushvalue(lua, -1);
// Lua: ExposedEvent, lambda, lambda
auto handlerAndType = AZStd::invoke(holder.m_function, userData->value, AZStd::move(ExposedLambda(lua)));
// Lua: ExposedEvent, lambda
Internal::RegisteredObjectToLua(lua, handlerAndType.m_address, handlerAndType.m_typeId, ObjectToLua::ByReference, AcquisitionOnPush::ScriptAcquire);
// Lua: ExposedEvent, lambda, handler
return 1;
}
//////////////////////////////////////////////////////////////////////////
void BindEventSupport()
{
// Lua: ..., class_mt
lua_pushliteral(m_lua, "Connect");
// Lua: ..., class_mt, "Connect"
lua_pushcfunction(m_lua, &ConnectToExposedEvent);
// Lua: ..., class_mt, "Connect", ConnectToExposedEvent
lua_rawset(m_lua, -3);
// Lua: ..., class_mt
}
//////////////////////////////////////////////////////////////////////////
void BindTo(BehaviorContext* behaviorContext)
@@ -5674,7 +5876,7 @@ LUA_API const Node* lua_getDummyNode()
}
//////////////////////////////////////////////////////////////////////////
bool ScriptContext::Call(const char* functionName, ScriptDataContext& dc, bool warnIfNotFound)
bool ScriptContext::Call(const char* functionName, ScriptDataContext& dc)
{
dc.Reset();
Internal::azlua_getglobal(m_impl->m_lua, functionName);
@@ -5686,10 +5888,7 @@ LUA_API const Node* lua_getDummyNode()
else
{
lua_pop(m_impl->m_lua, 1);
if (warnIfNotFound)
{
AZ_Warning("Script", false, "%s is not a function!", functionName);
}
AZ_Warning("Script", false, "%s is not a function!", functionName);
}
return false;
}
@@ -888,7 +888,7 @@ namespace AZ
int CacheGlobal(const char* name);
/// Release any cached resource (global or local)
void ReleaseCached(int cacheIndex);
bool Call(const char* functionName, ScriptDataContext& dc, bool warnIfNotFound = true);
bool Call(const char* functionName, ScriptDataContext& dc);
bool CallCached(int cachedIndex, ScriptDataContext& dc);
bool InspectTable(const char* tableName, ScriptDataContext& dc);
@@ -35,6 +35,14 @@ namespace AZ
const static AZ::Crc32 DisallowBroadcast = AZ_CRC("DisallowBroadcast", 0x389b0ac7); ///< Marks a reflected EBus as not allowing Broadcasts, only Events.
const static AZ::Crc32 ClassConstantValue = AZ_CRC_CE("ClassConstantValue"); ///< Indicates the property is backed by a constant value
//! Attribute which stores BehaviorAzEventDescription structure which contains
//! the script name of an AZ::Event and the name of it's parameter arguments
static constexpr AZ::Crc32 AzEventDescription = AZ_CRC_CE("AzEventDescription");
//! Applied to AZ Event reflected classes.
//! Stores a vector<TypeId> containing the Uuid of each event param
static constexpr AZ::Crc32 EventParameterTypes = AZ_CRC_CE("EventParameterTypes");
///< Recommends that the Lua runtime look up the member function in the meta table of the first argument, rather than in the original table
const static AZ::Crc32 TreatAsMemberFunction = AZ_CRC("TreatAsMemberFunction", 0x64be831a);
@@ -46,14 +46,14 @@ namespace AZ
const AZStd::chrono::system_clock::time_point& Get() { return m_timePoint; }
// Returns the time point in seconds
double GetSeconds()
double GetSeconds() const
{
typedef AZStd::chrono::duration<double> double_seconds;
return AZStd::chrono::duration_cast<double_seconds>(m_timePoint.time_since_epoch()).count();
}
// Returns the time point in milliseconds
double GetMilliseconds()
double GetMilliseconds() const
{
typedef AZStd::chrono::duration<double, AZStd::milli> double_ms;
return AZStd::chrono::duration_cast<double_ms>(m_timePoint.time_since_epoch()).count();
@@ -28,8 +28,8 @@ namespace AZ
AZ_RTTI(JsonRegistrationContext, "{5A763774-CA8B-4245-A897-A03C503DCD60}", ReflectContext);
class SerializerBuilder;
using SerializerMap = AZStd::unordered_map<const Uuid&, AZStd::unique_ptr<BaseJsonSerializer>, AZStd::hash<Uuid>>;
using HandledTypesMap = AZStd::unordered_map<const Uuid&, BaseJsonSerializer*, AZStd::hash<Uuid>>;
using SerializerMap = AZStd::unordered_map<Uuid, AZStd::unique_ptr<BaseJsonSerializer>, AZStd::hash<Uuid>>;
using HandledTypesMap = AZStd::unordered_map<Uuid, BaseJsonSerializer*, AZStd::hash<Uuid>>;
~JsonRegistrationContext() override;
@@ -526,6 +526,42 @@ namespace AZ
return m_editContext;
}
auto SerializeContext::RegisterType(const AZ::TypeId& typeId, AZ::SerializeContext::ClassData&& classData, CreateAnyFunc createAnyFunc) -> ClassBuilder
{
auto [typeToClassIter, inserted] = m_uuidMap.try_emplace(typeId, AZStd::move(classData));
m_classNameToUuid.emplace(AZ::Crc32(typeToClassIter->second.m_name), typeId);
m_uuidAnyCreationMap.emplace(typeId, createAnyFunc);
return ClassBuilder(this, typeToClassIter);
}
bool SerializeContext::UnregisterType(const AZ::TypeId& typeId)
{
if (auto typeToClassIter = m_uuidMap.find(typeId); typeToClassIter != m_uuidMap.end())
{
ClassData& classData = typeToClassIter->second;
RemoveClassData(&classData);
auto [classNameRangeFirst, classNameRangeLast] = m_classNameToUuid.equal_range(Crc32(classData.m_name));
while (classNameRangeFirst != classNameRangeLast)
{
if (classNameRangeFirst->second == typeId)
{
classNameRangeFirst = m_classNameToUuid.erase(classNameRangeFirst);
}
else
{
++classNameRangeFirst;
}
}
m_uuidAnyCreationMap.erase(typeId);
m_uuidMap.erase(typeToClassIter);
return true;
}
return false;
}
//=========================================================================
// ClassDeprecate
// [11/8/2012]
@@ -145,7 +145,7 @@ namespace AZ
typedef AZStd::unordered_map<Uuid, ClassData> UuidToClassMap;
/// If registerIntegralTypes is true we will register the default serializer for all integral types.
SerializeContext(bool registerIntegralTypes = true, bool createEditContext = false);
explicit SerializeContext(bool registerIntegralTypes = true, bool createEditContext = false);
virtual ~SerializeContext();
/// Deleting copy ctor because we own unique_ptr's of IDataContainers
@@ -185,6 +185,17 @@ namespace AZ
template<typename EnumType>
EnumBuilder Enum(IObjectFactory* factory);
//! Function Pointer which is used to construct an AZStd::any for a registered type using the Serialize Context
using CreateAnyFunc = AZStd::any(*)(SerializeContext*);
//! Allows registration of a TypeId without the need to supply a C++ type
//! If the type is not already registered, then the ClassData is moved into the SerializeContext
//! internal structure
ClassBuilder RegisterType(const AZ::TypeId& typeId, AZ::SerializeContext::ClassData&& classData,
CreateAnyFunc createAnyFunc = [](SerializeContext*) -> AZStd::any { return {}; });
//! Unregister a type from the SerializeContext and removes all internal mappings
//! @return true if the type was previously registered
bool UnregisterType(const AZ::TypeId& typeId);
// Helper method that gets the generic info of ValueType and calls Reflect on it, should it exist
template <class ValueType>
void RegisterGenericType();
@@ -1055,7 +1066,6 @@ namespace AZ
AZStd::any CreateAny(const Uuid& classId);
/// Register GenericClassInfo with the SerializeContext
using CreateAnyFunc = AZStd::any(*)(SerializeContext*);
void RegisterGenericClassInfo(const AZ::Uuid& typeId, GenericClassInfo* genericClassInfo, const CreateAnyFunc& createAnyFunc);
/// Unregisters all GenericClassInfo instances registered in the current module and deletes the GenericClassInfo instances
@@ -1091,11 +1101,6 @@ namespace AZ
const TypeId& GetUnderlyingTypeId(const TypeId& enumTypeId) const;
private:
/**
* Generic enumerate function can can take both 'const void*' and 'void*' data pointer types.
*/
template<class PtrType, class EnumType>
bool EnumerateInstanceTempl(PtrType ptr, const Uuid& classId, EnumType beginElemCB, const SerializeContext::EndElemEnumCB& endElemCB, const ClassData* classData, const char* elementName, const ClassElement* classElement);
/// Enumerate function called to enumerate an azrtti hierarchy
static void EnumerateBaseRTTIEnumCallback(const Uuid& id, void* userData);
@@ -1059,6 +1059,15 @@ namespace AZ
}
u64 fileSize = file.Length();
if (fileSize == 0)
{
AZ_Warning("Settings Registry", false, R"(Registry file "%s" is 0 bytes in length. There is no nothing to merge)", path);
pointer.Create(m_settings, m_settings.GetAllocator())
.SetObject()
.AddMember(StringRef("Error"), StringRef("registry file is 0 bytes."), m_settings.GetAllocator())
.AddMember(StringRef("Path"), Value(path, m_settings.GetAllocator()), m_settings.GetAllocator());
return false;
}
scratchBuffer.clear();
scratchBuffer.resize_no_construct(fileSize + 1);
if (file.Read(fileSize, scratchBuffer.data()) != fileSize)
@@ -1076,8 +1085,17 @@ namespace AZ
jsonPatch.ParseInsitu<flags>(scratchBuffer.data());
if (jsonPatch.HasParseError())
{
AZ_Error("Settings Registry", false, R"(Unable to parse registry file "%s" due to json error "%s" at offset %llu.)",
path, GetParseError_En(jsonPatch.GetParseError()), jsonPatch.GetErrorOffset());
if (jsonPatch.GetParseError() == rapidjson::kParseErrorDocumentEmpty)
{
AZ_Warning("Settings Registry", false, R"(Unable to parse registry file "%s" due to json error "%s" at offset %llu.)",
path, GetParseError_En(jsonPatch.GetParseError()), jsonPatch.GetErrorOffset());
}
else
{
AZ_Error("Settings Registry", false, R"(Unable to parse registry file "%s" due to json error "%s" at offset %llu.)", path,
GetParseError_En(jsonPatch.GetParseError()), jsonPatch.GetErrorOffset());
}
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Unable to parse registry file due to invalid json."), m_settings.GetAllocator())
.AddMember(StringRef("Path"), Value(path, m_settings.GetAllocator()), m_settings.GetAllocator())
@@ -211,6 +211,8 @@ namespace AZ::SettingsRegistryMergeUtils
void MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(SettingsRegistryInterface& registry, AZStd::string_view targetName)
{
registry.Set(BuildTargetNameKey, targetName);
// Add specializations to the target registry based on the name of the Build System Target
auto targetSpecialization = AZ::SettingsRegistryInterface::FixedValueString::format("%s/%.*s",
SpecializationsRootKey, aznumeric_cast<int>(targetName.size()), targetName.data());
@@ -305,7 +307,7 @@ namespace AZ::SettingsRegistryMergeUtils
// Iterate over the line and escape the '~' and '/' values
AZStd::fixed_string<ConfigBufferMaxSize> escapedLine = EncodeLineForJsonPointer<ConfigBufferMaxSize>(line,
configParserSettings.m_commandLineSettings.m_delimiterFunc);
registry.MergeCommandLineArgument(escapedLine, currentJsonPointerPath, configParserSettings.m_commandLineSettings);
// Skip past the newline character if found
@@ -410,6 +412,13 @@ namespace AZ::SettingsRegistryMergeUtils
#endif
registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
// check for a default write storage path, fall back to the cache root if not
AZStd::optional<AZ::IO::FixedMaxPathString> devWriteStorage = Utils::GetDevWriteStoragePath();
registry.Set(FilePathKey_DevWriteStorage,
devWriteStorage.has_value() ?
devWriteStorage.value() :
path.LexicallyNormal().Native());
// Cache game folder - corresponds to the @assets@ alias
AZStd::to_lower(projectPathValue.begin(), projectPathValue.end());
path /= projectPathValue;
@@ -614,7 +623,7 @@ namespace AZ::SettingsRegistryMergeUtils
switchKey += '/';
switchKey += commandOption;
size_t switchKeyRootSize = switchKey.size();
// Associate an empty array with the commandOption by default
// Associate an empty array with the commandOption by default
rapidjson::Document commandSwitchDocument;
rapidjson::Pointer pointer(switchKey.c_str(), switchKey.length());
pointer.Set(commandSwitchDocument, rapidjson::Value(rapidjson::kArrayType));;
@@ -29,6 +29,7 @@ namespace AZ::IO
namespace AZ::SettingsRegistryMergeUtils
{
inline static constexpr char OrganizationRootKey[] = "/Amazon";
inline static constexpr char BuildTargetNameKey[] = "/Amazon/AzCore/Settings/BuildTargetName";
inline static constexpr char SpecializationsRootKey[] = "/Amazon/AzCore/Settings/Specializations";
inline static constexpr char BootstrapSettingsRootKey[] = "/Amazon/AzCore/Bootstrap";
inline static constexpr char GemListRootKey[] = "/Amazon/AzCore/Gems";
@@ -40,12 +41,14 @@ namespace AZ::SettingsRegistryMergeUtils
inline static constexpr char FilePathKey_SourceGameFolder[] = "/Amazon/AzCore/Runtime/FilePaths/SourceGameFolder";
//! Stores the filename of the Game Project Directory which is equivalent to the project name
inline static constexpr char FilePathKey_SourceGameName[] = "/Amazon/AzCore/Runtime/FilePaths/SourceGameName";
//! Development write storage path may be considered temporary or cache storage on some platforms
inline static constexpr char FilePathKey_DevWriteStorage[] = "/Amazon/AzCore/Runtime/FilePaths/DevWriteStorage";
//! Root key for where command line are stored at witin the settings registry
inline static constexpr char CommandLineRootKey[] = "/Amazon/AzCore/Runtime/CommandLine";
//! Root key for command line switches(arguments that start with "-" or "--")
inline static constexpr char CommandLineSwitchRootKey[] = "/Amazon/AzCore/Runtime/CommandLine/Switches";
//! Root key for command line positional arguments
//! Root key for command line positional arguments
inline static constexpr char CommandLineMiscValuesRootKey[] = "/Amazon/AzCore/Runtime/CommandLine/MiscValues";
//! Examines the Settings Registry for a "/Amazon/CommandLine/Switches/app-root" key
@@ -174,7 +177,7 @@ namespace AZ::SettingsRegistryMergeUtils
//! Determines if a PrettyWriter should be used when dumping the Settings Registry
bool m_prettifyOutput{};
//! Include filter which is used to indicate which paths of the Settings Registry
//! should be traversed.
//! should be traversed.
//! If the include filter is empty then all paths underneath the JSON pointer path are included
//! otherwise the include filter invoked and if it returns true does it proceed with traversal continues down the path
AZStd::function<bool(AZStd::string_view path)> m_includeFilter;
@@ -18,15 +18,38 @@
namespace AZ::SettingsRegistryScriptUtils::Internal
{
static void RegisterScriptProxyForNotify(SettingsRegistryScriptProxy& settingsRegistryProxy)
{
if (settingsRegistryProxy.IsValid())
{
auto ForwardSettingsUpdateToProxyEvent = [&settingsRegistryProxy](AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{
if (settingsRegistryProxy.m_notifyEventProxy)
{
settingsRegistryProxy.m_notifyEventProxy->m_scriptNotifyEvent.Signal(path);
}
};
// Register the forwarding function with the BehaviorContext
settingsRegistryProxy.m_notifyEventProxy->m_settingsUpdatedHandler =
settingsRegistryProxy.m_settingsRegistry->RegisterNotifier(ForwardSettingsUpdateToProxyEvent);
}
}
SettingsRegistryScriptProxy::SettingsRegistryScriptProxy() = default;
SettingsRegistryScriptProxy::SettingsRegistryScriptProxy(AZStd::shared_ptr<AZ::SettingsRegistryInterface> settingsRegistry)
: m_settingsRegistry(AZStd::move(settingsRegistry))
{}
, m_notifyEventProxy(AZStd::make_shared<NotifyEventProxy>())
{
RegisterScriptProxyForNotify(*this);
}
// Raw AZ::SettingsRegistryInterface pointer is not owned by the proxy, so it's deleter is a no-op
SettingsRegistryScriptProxy::SettingsRegistryScriptProxy(AZ::SettingsRegistryInterface* const settingsRegistry)
: m_settingsRegistry(settingsRegistry, [](AZ::SettingsRegistryInterface*) {})
{}
, m_notifyEventProxy(AZStd::make_shared<NotifyEventProxy>())
{
RegisterScriptProxyForNotify(*this);
}
// SettingsRegistryScriptProxy function that determines if the SettingsRegistry object is valid
bool SettingsRegistryScriptProxy::IsValid() const
@@ -69,23 +92,23 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
// Reflect Specializations structure
auto SpecializationsAppend = [](SpecializationsProxy* tagInst, AZStd::string_view specialization)
{
return tagInst->m_specializations->Append(specialization);
return tagInst && tagInst->m_specializations->Append(specialization);
};
auto SpecializationsContains = [](SpecializationsProxy* tagInst, AZStd::string_view specialization)
{
return tagInst->m_specializations->Contains(specialization);
return tagInst && tagInst->m_specializations->Contains(specialization);
};
auto SpecializationsGetPriority = [](SpecializationsProxy* tagInst, AZStd::string_view specialization)
{
return tagInst->m_specializations->GetPriority(specialization);
return tagInst ? tagInst->m_specializations->GetPriority(specialization) : 0U;
};
auto SpecializationsGetCount = [](SpecializationsProxy* tagInst)
{
return tagInst->m_specializations->GetCount();
return tagInst ? tagInst->m_specializations->GetCount() : 0U;
};
auto SpecializationsGetSpecialization = [](SpecializationsProxy* tagInst, size_t index)
{
return tagInst->m_specializations->GetSpecialization(index);
return tagInst ? tagInst->m_specializations->GetSpecialization(index) : AZStd::string_view{};
};
behaviorContext.Class<SpecializationsProxy>("Specializations")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
@@ -121,7 +144,8 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
auto MergeSettings = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view jsonData,
AZ::SettingsRegistryInterface::Format format) -> bool
{
return settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->MergeSettings(jsonData, format);
return settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->MergeSettings(jsonData, format);
};
// Set a default value for the Setting Registry Merge Format parameter
// This allows the function to be called from the BehaviorContext using only the json data parameter
@@ -132,7 +156,7 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
auto MergeSettingsFile = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view filePath,
AZStd::string_view jsonRootKey, AZ::SettingsRegistryInterface::Format format) -> bool
{
return settingsRegistryProxy->IsValid()
return settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->MergeSettingsFile(filePath, format, jsonRootKey);
};
using MergeSettingsFileFunctionTraits = AZStd::function_traits<decltype(MergeSettingsFile)>;
@@ -153,7 +177,7 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
auto MergeSettingsFolder = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view folderPath,
const Internal::SpecializationsProxy& specProxy, AZStd::string_view platform) -> bool
{
return settingsRegistryProxy->IsValid()
return settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->MergeSettingsFolder(folderPath, *specProxy.m_specializations, platform);
};
using MergeSettingsFolderFunctionTraits = AZStd::function_traits<decltype(MergeSettingsFolder)>;
@@ -173,24 +197,29 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
auto SetBool = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view jsonPath, bool boolValue) -> bool
{
return settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->Set(jsonPath, boolValue);
return settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->Set(jsonPath, boolValue);
};
auto SetInt = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view jsonPath, AZ::s64 intValue) -> bool
{
return settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->Set(jsonPath, intValue);
return settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->Set(jsonPath, intValue);
};
auto SetUint = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view jsonPath, AZ::u64 uintValue) -> bool
{
return settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->Set(jsonPath, uintValue);
return settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->Set(jsonPath, uintValue);
};
auto SetFloat = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view jsonPath, double floatValue) -> bool
{
return settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->Set(jsonPath, floatValue);
return settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->Set(jsonPath, floatValue);
};
auto SetString = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view jsonPath, AZStd::string_view stringValue)
-> bool
{
return settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->Set(jsonPath, stringValue);
return settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->Set(jsonPath, stringValue);
};
// Query functors
@@ -200,8 +229,9 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
AZStd::string outputString;
AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings{ true };
AZ::IO::ByteContainerStream outputStream(&outputString);
if (settingsRegistryProxy->IsValid() && AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream(
*settingsRegistryProxy->m_settingsRegistry, jsonPath, outputStream, dumperSettings))
if (settingsRegistryProxy && settingsRegistryProxy->IsValid() &&
AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream(*settingsRegistryProxy->m_settingsRegistry,
jsonPath, outputStream, dumperSettings))
{
return outputString;
}
@@ -212,7 +242,8 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
auto GetBool = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view jsonPath) -> AZStd::optional<bool>
{
bool boolValue{};
if (settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->Get(boolValue, jsonPath))
if (settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->Get(boolValue, jsonPath))
{
return boolValue;
}
@@ -222,7 +253,8 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
auto GetInt = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view jsonPath) -> AZStd::optional<s64>
{
AZ::s64 intValue{};
if (settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->Get(intValue, jsonPath))
if (settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->Get(intValue, jsonPath))
{
return intValue;
}
@@ -232,7 +264,8 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
auto GetUint = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view jsonPath) -> AZStd::optional<u64>
{
AZ::u64 uintValue{};
if (settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->Get(uintValue, jsonPath))
if (settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->Get(uintValue, jsonPath))
{
return uintValue;
}
@@ -242,7 +275,8 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
auto GetFloat = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view jsonPath) -> AZStd::optional<double>
{
double floatValue{};
if (settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->Get(floatValue, jsonPath))
if (settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->Get(floatValue, jsonPath))
{
return floatValue;
}
@@ -252,7 +286,7 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
auto GetString = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view jsonPath) -> AZStd::optional<AZStd::string>
{
AZStd::string stringValue;
if (settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->Get(stringValue, jsonPath))
if (settingsRegistryProxy && settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->Get(stringValue, jsonPath))
{
return stringValue;
}
@@ -263,7 +297,23 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
// SettingsRegistry::Remove wrapper
auto RemoveKey = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy, AZStd::string_view jsonPath) -> bool
{
return settingsRegistryProxy->IsValid() && settingsRegistryProxy->m_settingsRegistry->Remove(jsonPath);
return settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_settingsRegistry->Remove(jsonPath);
};
// Reflect function which returns the ScriptNotifyEvent from the SettingsRegistryScriptProxy
// in order to trigger the ScriptCanvas feature of allowing creation of an AZ EventHandler
// from a BehaviorMethod node which returns an AZ::Event pointer
auto GetScriptNotifyEvent = [](Internal::SettingsRegistryScriptProxy* settingsRegistryProxy)
-> Internal::SettingsRegistryScriptProxy::ScriptNotifyEvent*
{
if (settingsRegistryProxy && settingsRegistryProxy->IsValid()
&& settingsRegistryProxy->m_notifyEventProxy)
{
return &settingsRegistryProxy->m_notifyEventProxy->m_scriptNotifyEvent;
}
return nullptr;
};
auto settingsRegistryClassBuilder = behaviorContext.Class<Internal::SettingsRegistryScriptProxy>(InterfaceClassName);
@@ -287,6 +337,16 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
->Method("GetString", GetString)
->Method("RemoveKey", RemoveKey)
;
// Create the BehaviorAzEventDescription needed to reflect the
// GetNotifyEvent method to the BehaviorContext without errors
AZ::BehaviorAzEventDescription scriptNotifyEventDesc;
scriptNotifyEventDesc.m_eventName = "SettingsRegistry Notify Event";
scriptNotifyEventDesc.m_parameterNames.push_back("Json Path");
settingsRegistryClassBuilder->Method("GetNotifyEvent", GetScriptNotifyEvent)
->Attribute(AZ::Script::Attributes::AzEventDescription, AZStd::move(scriptNotifyEventDesc))
;
}
void ReflectSettingsRegistryCreateMethod(AZ::BehaviorContext& behaviorContext)
@@ -12,12 +12,14 @@
#pragma once
#include <AzCore/Settings/SettingsRegistry.h>
namespace AZ
{
class BehaviorContext;
class SettingsRegistryInterface;
}
namespace AZ::SettingsRegistryScriptUtils
{
//! Reflects the SettingsRegistryInterface class to the BehaviorContext.
@@ -34,6 +36,18 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
AZ_CLASS_ALLOCATOR(SettingsRegistryScriptProxy, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(SettingsRegistryScriptProxy, "{795C80A0-D243-473B-972A-C32CA487BAA5}");
// NotifyEventProxy is used to forward an update to an entry in the SettingsRegistry
// using the RegisterNotifier function to the BehaviorContext in order to create
// a behavior method that returns an AZ::Event reference or pointer
// This allows ScriptCanvas to create an AZ Event Handler using the information
// reflected to the BehaviorMethod
using ScriptNotifyEvent = AZ::Event<AZStd::string_view>;
struct NotifyEventProxy
{
ScriptNotifyEvent m_scriptNotifyEvent;
AZ::SettingsRegistryInterface::NotifyEventHandler m_settingsUpdatedHandler;
};
SettingsRegistryScriptProxy();
// Stores a SettingsRegistryInterface which will use the provided shared_ptr deleter when the reference count hits zero
SettingsRegistryScriptProxy(AZStd::shared_ptr<AZ::SettingsRegistryInterface> settingsRegistry);
@@ -44,5 +58,6 @@ namespace AZ::SettingsRegistryScriptUtils::Internal
bool IsValid() const;
AZStd::shared_ptr<AZ::SettingsRegistryInterface> m_settingsRegistry;
AZStd::shared_ptr<NotifyEventProxy> m_notifyEventProxy;
};
}
@@ -16,8 +16,8 @@
namespace AZ
{
AZ_TYPE_SAFE_INTEGRAL(HashValue32, uint32_t);
AZ_TYPE_SAFE_INTEGRAL(HashValue64, uint64_t);
AZ_TYPE_SAFE_INTEGRAL(HashValue32, u32);
AZ_TYPE_SAFE_INTEGRAL(HashValue64, u64);
//! Hashes a contiguous array of bytes starting at buffer and ending at buffer + length
//! @param[in] buffer pointer to the memory to be hashed
+8 -3
View File
@@ -14,6 +14,7 @@
#include <AzCore/PlatformDef.h>
#include <AzCore/base.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/optional.h>
#include <AzCore/std/string/string.h>
@@ -44,7 +45,7 @@ namespace AZ
//! has closed the message box.
void NativeErrorMessageBox(const char* title, const char* message);
//! Enum used for the GetExecutablePath return type which indicates
//! Enum used for the GetExecutablePath return type which indicates
//! whether the function returned with a success value or a specific error
enum class ExecutablePathResult : int8_t
{
@@ -66,14 +67,14 @@ namespace AZ
//! Retrieves the path to the application executable
//! @param exeStorageBuffer output buffer which is used to store the executable path within
//! @param exeStorageSize size of the exeStorageBuffer
//! @returns a struct that indicates if the executable path was able to be stored within the executableBuffer
//! @returns a struct that indicates if the executable path was able to be stored within the executableBuffer
//! as well as if the executable path contains the executable filename or the executable directory
GetExecutablePathReturnType GetExecutablePath(char* exeStorageBuffer, size_t exeStorageSize);
//! Retrieves the directory of the application executable
//! @param exeStorageBuffer output buffer which is used to store the executable path within
//! @param exeStorageSize size of the exeStorageBuffer
//! @returns a result object that indicates if the executable directory was able to be stored within the buffer
//! @returns a result object that indicates if the executable directory was able to be stored within the buffer
ExecutablePathResult GetExecutableDirectory(char* exeStorageBuffer, size_t exeStorageSize);
//! Retrieves the App root path to use on the current platform
@@ -81,6 +82,10 @@ namespace AZ
//! on the location of the bootstrap.cfg file
AZStd::optional<AZStd::fixed_string<MaxPathLength>> GetDefaultAppRootPath();
//! Retrieves the development write storage path to use on the current platform, may be considered
//! temporary or cache storage
AZStd::optional<AZ::IO::FixedMaxPathString> GetDevWriteStoragePath();
// Attempts the supplied path to an absolute path.
//! Returns nullopt if path cannot be converted to an absolute path
AZStd::optional<AZStd::fixed_string<MaxPathLength>> ConvertToAbsolutePath(AZStd::string_view path);
@@ -51,6 +51,8 @@ set(FILES
Component/EntityId.h
Component/EntityIdSerializer.cpp
Component/EntityIdSerializer.h
Component/EntitySerializer.cpp
Component/EntitySerializer.h
Component/EntityUtils.cpp
Component/EntityUtils.h
Component/NamedEntityId.cpp
@@ -73,9 +75,6 @@ set(FILES
Console/ILogger.h
Console/LoggerSystemComponent.cpp
Console/LoggerSystemComponent.h
Prefab/PrefabAsset.h
Prefab/PrefabBus.h
Prefab/PrefabComponent.h
Slice/SliceAsset.cpp
Slice/SliceAsset.h
Slice/SliceAssetHandler.cpp
@@ -96,10 +95,13 @@ set(FILES
Debug/AssetTracking.h
Debug/AssetTrackingTypesImpl.h
Debug/AssetTrackingTypes.h
Debug/LocalFileEventLogger.h
Debug/LocalFileEventLogger.cpp
Debug/FrameProfiler.h
Debug/FrameProfilerBus.h
Debug/FrameProfilerComponent.cpp
Debug/FrameProfilerComponent.h
Debug/IEventLogger.h
Debug/ProfileModuleInit.cpp
Debug/ProfileModuleInit.h
Debug/Profiler.cpp
@@ -274,7 +276,7 @@ set(FILES
Math/Internal/SimdMathCommon_neonDouble.inl
Math/Internal/SimdMathCommon_neonQuad.inl
Math/Internal/SimdMathCommon_simd.inl
Math/Internal/SimdMathCommon_sse.inl
Math/Internal/SimdMathCommon_sse.inl
Math/Internal/VectorConversions.inl
Math/Internal/VertexContainer.inl
Math/InterpolationSample.h
@@ -286,7 +288,7 @@ set(FILES
Math/MathReflection.h
Math/MathScriptHelpers.cpp
Math/MathScriptHelpers.h
Math/MathUtils.cpp
Math/MathUtils.cpp
Math/MathUtils.h
Math/MathVectorSerializer.h
Math/MathVectorSerializer.cpp
@@ -988,6 +988,8 @@ namespace AZStd
return ++result;
}
using std::binary_search;
// todo search_n
//////////////////////////////////////////////////////////////////////////
+1 -1
View File
@@ -186,7 +186,7 @@ namespace AZStd
: any(alloc)
{
static_assert(std::is_copy_constructible<decay_t<ValueType>>::value
|| (std::is_rvalue_reference<ValueType>::value && std::is_move_constructible<decay_t<ValueType>>::value), "ValueType must be copy constructible or a movable rvalue ref.");
|| (std::is_rvalue_reference<ValueType&&>::value && std::is_move_constructible<decay_t<ValueType>>::value), "ValueType must be copy constructible or a movable rvalue ref.");
// Initialize typeinfo from the type given
m_typeInfo = create_template_type_info<decay_t<ValueType>>();
@@ -196,6 +196,54 @@ namespace AZStd
return m_tree.node_handle_insert_unique(hint, AZStd::move(nodeHandle));
}
//! C++17 insert_or_assign function assigns the element to the mapped_type if the key exist in the container
//! Otherwise a new value is inserted into the container
template <typename M>
pair<iterator, bool> insert_or_assign(const key_type& key, M&& value)
{
return m_tree.insert_or_assign_unique(key, AZStd::forward<M>(value));
}
template <typename M>
pair<iterator, bool> insert_or_assign(key_type&& key, M&& value)
{
return m_tree.insert_or_assign_unique(AZStd::move(key), AZStd::forward<M>(value));
}
template <typename M>
iterator insert_or_assign(const_iterator hint, const key_type& key, M&& value)
{
return m_tree.insert_or_assign_unique(hint, key, AZStd::forward<M>(value));
}
template <typename M>
iterator insert_or_assign(const_iterator hint, key_type&& key, M&& value)
{
return m_tree.insert_or_assign_unique(hint, AZStd::move(key), AZStd::forward<M>(value));
}
//! C++17 try_emplace function that does nothing to the arguments if the key exist in the container,
//! otherwise it constructs the value type as if invoking
//! value_type(AZStd::piecewise_construct, AZStd::forward_as_tuple(AZStd::forward<KeyType>(key)),
//! AZStd::forward_as_tuple(AZStd::forward<Args>(args)...))
template <typename... Args>
pair<iterator, bool> try_emplace(const key_type& key, Args&&... arguments)
{
return m_tree.try_emplace_unique(key, AZStd::forward<Args>(arguments)...);
}
template <typename... Args>
pair<iterator, bool> try_emplace(key_type&& key, Args&&... arguments)
{
return m_tree.try_emplace_unique(AZStd::move(key), AZStd::forward<Args>(arguments)...);
}
template <typename... Args>
iterator try_emplace(const_iterator hint, const key_type& key, Args&&... arguments)
{
return m_tree.try_emplace_unique(hint, key, AZStd::forward<Args>(arguments)...);
}
template <typename... Args>
iterator try_emplace(const_iterator hint, key_type&& key, Args&&... arguments)
{
return m_tree.try_emplace_unique(hint, AZStd::move(key), AZStd::forward<Args>(arguments)...);
}
node_type extract(const key_type& key)
{
return m_tree.template node_handle_extract<node_type>(key);
@@ -199,7 +199,7 @@ namespace AZStd
void push(value_type&& value) { m_container.push_back(AZStd::move(value)); AZStd::push_heap(m_container.begin(), m_container.end(), m_comp); }
template<class Args>
void emplace(Args&& args) { m_container.emplace_back(AZStd::forward<Args>(args)); AZStd::push_heap(m_container.begin(), m_container.end(), m_comp); }
void swap(this_type& rhs) { AZStd::swap(m_container, rhs.m_continer); AZStd::swap(m_comp, rhs.m_comp); }
void swap(this_type& rhs) { AZStd::swap(m_container, rhs.m_container); AZStd::swap(m_comp, rhs.m_comp); }
AZ_FORCE_INLINE Container& get_container() { return m_container; }
AZ_FORCE_INLINE const Container& get_container() const { return m_container; }
@@ -9,8 +9,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZSTD_RED_BLACK_TREE_H
#define AZSTD_RED_BLACK_TREE_H
#pragma once
#include <AzCore/std/allocator.h>
#include <AzCore/std/allocator_traits.h>
@@ -18,6 +17,7 @@
#include <AzCore/std/createdestroy.h>
#include <AzCore/std/functional_basic.h>
#include <AzCore/std/typetraits/alignment_of.h>
#include <AzCore/std/tuple.h>
#include <AzCore/std/utils.h>
// Allow us to save up to 25% of the node overhead. And yes the code is faster too.
@@ -730,7 +730,7 @@ namespace AZStd
}
return result;
}
iterator insert_equal(iterator insertPos, const value_type& value)
iterator insert_equal(const iterator insertPos, const value_type& value)
{
return insert_equal_node(insertPos, create_node(value));
}
@@ -752,6 +752,18 @@ namespace AZStd
}
}
template <typename ComparableToKey, typename... Args>
AZStd::pair<iterator, bool> try_emplace_unique(ComparableToKey&& key, Args&&... arguments);
template <typename ComparableToKey, typename... Args>
iterator try_emplace_unique(const_iterator hint, ComparableToKey&& key, Args&&... arguments);
template <typename ComparableToKey, typename MappedType>
AZStd::pair<iterator, bool> insert_or_assign_unique(ComparableToKey&& key, MappedType&& value);
template <typename ComparableToKey, typename MappedType>
iterator insert_or_assign_unique(const_iterator hint, ComparableToKey&& key, MappedType&& value);
//! Returns an insert_return_type with the members initialized as follows: if nodeHandle is empty, inserted is false, position is end(), and node is empty.
//! Otherwise if the insertion took place, inserted is true, position points to the inserted element, and node is empty.
//! If the insertion failed, inserted is false, node has the previous value of nodeHandle, and position points to an element with a key equivalent to nodeHandle.key().
@@ -2077,8 +2089,71 @@ namespace AZStd
return NodeHandle{ nodeToExtract, get_allocator() };
}
}
template <class Traits>
template <typename ComparableToKey, typename... Args>
auto rbtree<Traits>::try_emplace_unique(ComparableToKey&& key, Args&&... arguments) -> AZStd::pair<iterator, bool>
{
// Check if the key has a corresponding node in the container
iterator insertIter = lower_bound(key);
if (insertIter.m_node != &m_head && !m_keyEq(key, insertIter->first))
{
return { insertIter, false };
}
#endif // AZSTD_RED_BLACK_TREE_H
#pragma once
base_node_ptr_type newNode = create_node(AZStd::piecewise_construct, AZStd::forward_as_tuple(AZStd::forward<ComparableToKey>(key)),
AZStd::forward_as_tuple(AZStd::forward<Args>(arguments)...));
return { insert_unique_node(insertIter, newNode), true };
}
template <class Traits>
template <typename ComparableToKey, typename... Args>
auto rbtree<Traits>::try_emplace_unique(const_iterator hint, ComparableToKey&& key, Args&&... arguments) -> iterator
{
// Check if the key has a corresponding node in the container
iterator insertIter = lower_bound(key);
if (insertIter.m_node != &m_head && !m_keyEq(key, insertIter->first))
{
return insertIter;
}
base_node_ptr_type newNode = create_node(AZStd::piecewise_construct, AZStd::forward_as_tuple(AZStd::forward<ComparableToKey>(key)),
AZStd::forward_as_tuple(AZStd::forward<Args>(arguments)...));
return insert_unique_node(hint, newNode);
}
template <class Traits>
template <typename ComparableToKey, typename MappedType>
auto rbtree<Traits>::insert_or_assign_unique(ComparableToKey&& key, MappedType&& value) -> AZStd::pair<iterator, bool>
{
// Check if the key has a corresponding node in the container
iterator insertIter = lower_bound(key);
if (insertIter.m_node != &m_head && !m_keyEq(key, insertIter->first))
{
// Update the mapped element if the key has been found
insertIter->second = AZStd::forward<MappedType>(value);
return { insertIter, false };
}
base_node_ptr_type newNode = create_node(AZStd::piecewise_construct, AZStd::forward_as_tuple(AZStd::forward<ComparableToKey>(key)),
AZStd::forward_as_tuple(AZStd::forward<MappedType>(value)));
return { insert_unique_node(insertIter, newNode), true };
}
template <class Traits>
template <typename ComparableToKey, typename MappedType>
auto rbtree<Traits>::insert_or_assign_unique(const_iterator hint, ComparableToKey&& key, MappedType&& value) -> iterator
{
// Check if the key has a corresponding node in the container
iterator insertIter = lower_bound(key);
if (insertIter.m_node != &m_head && !m_keyEq(key, insertIter->first))
{
// Update the mapped element if the key has been found
insertIter->second = AZStd::forward<MappedType>(value);
return insertIter;
}
base_node_ptr_type newNode = create_node(AZStd::piecewise_construct, AZStd::forward_as_tuple(AZStd::forward<ComparableToKey>(key)),
AZStd::forward_as_tuple(AZStd::forward<MappedType>(value)));
return insert_unique_node(hint, newNode);
}
}
@@ -236,6 +236,54 @@ namespace AZStd
return base_type::node_handle_insert(hint, AZStd::move(nodeHandle));
}
//! C++17 insert_or_assign function assigns the element to the mapped_type if the key exist in the container
//! Otherwise a new value is inserted into the container
template <typename M>
pair_iter_bool insert_or_assign(const key_type& key, M&& value)
{
return base_type::insert_or_assign_transparent(key, AZStd::forward<M>(value));
}
template <typename M>
pair_iter_bool insert_or_assign(key_type&& key, M&& value)
{
return base_type::insert_or_assign_transparent(AZStd::move(key), AZStd::forward<M>(value));
}
template <typename M>
iterator insert_or_assign(const_iterator hint, const key_type& key, M&& value)
{
return base_type::insert_or_assign_transparent(hint, key, AZStd::forward<M>(value));
}
template <typename M>
iterator insert_or_assign(const_iterator hint, key_type&& key, M&& value)
{
return base_type::insert_or_assign_transparent(hint, AZStd::move(key), AZStd::forward<M>(value));
}
//! C++17 try_emplace function that does nothing to the arguments if the key exist in the container,
//! otherwise it constructs the value type as if invoking
//! value_type(AZStd::piecewise_construct, AZStd::forward_as_tuple(AZStd::forward<KeyType>(key)),
//! AZStd::forward_as_tuple(AZStd::forward<Args>(args)...))
template <typename... Args>
pair_iter_bool try_emplace(const key_type& key, Args&&... arguments)
{
return base_type::try_emplace_transparent(key, AZStd::forward<Args>(arguments)...);
}
template <typename... Args>
pair_iter_bool try_emplace(key_type&& key, Args&&... arguments)
{
return base_type::try_emplace_transparent(AZStd::move(key), AZStd::forward<Args>(arguments)...);
}
template <typename... Args>
iterator try_emplace(const_iterator hint, const key_type& key, Args&&... arguments)
{
return base_type::try_emplace_transparent(hint, key, AZStd::forward<Args>(arguments)...);
}
template <typename... Args>
iterator try_emplace(const_iterator hint, key_type&& key, Args&&... arguments)
{
return base_type::try_emplace_transparent(hint, AZStd::move(key), AZStd::forward<Args>(arguments)...);
}
node_type extract(const key_type& key)
{
return base_type::template node_handle_extract<node_type>(key);
+107 -35
View File
@@ -9,8 +9,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZSTD_HASH_TABLE_H
#define AZSTD_HASH_TABLE_H 1
#pragma once
#include <AzCore/Math/MathUtils.h>
#include <AzCore/std/containers/node_handle.h>
@@ -20,6 +19,7 @@
#include <AzCore/std/containers/fixed_list.h>
#include <AzCore/std/hash.h>
#include <AzCore/std/tuple.h>
#include <AzCore/std/utils.h>
#include <AzCore/std/functional_basic.h>
#include <AzCore/std/allocator_ref.h>
@@ -685,14 +685,6 @@ namespace AZStd
}
}
//AZ_FORCE_INLINE void erase(const key_type *first, const key_type *last)
//{
// // erase all that match array of keys [first, last)
// //_DEBUG_RANGE(first, last);
// for (; first != last; ++first)
// erase(*first);
//}
AZ_FORCE_INLINE void clear()
{
// erase all
@@ -959,28 +951,6 @@ namespace AZStd
protected:
AZ_FORCE_INLINE size_type bucket_from_hash(const size_type key) const { return key % m_data.get_num_buckets(); }
// FOR SLIST VERSION
/*AZ_FORCE_INLINE iterator find_previous(list_type& list, vector_value_type* buckets, iterator& iter, size_type bucketIndex)
{
if( iter != list.begin() )
{
while(bucketIndex>0)
{
--bucketIndex;
const vector_value_type& bucket = buckets[bucketIndex];
if(bucket.first > 0)
{
iterator iter = bucket.second;
for(size_type i = 0; i < (bucket.first-1); ++i, ++iter);
return iter;
}
}
}
return list.before_begin();
}*/
void copy(const this_type& rhs)
{
// copy entire hash table
@@ -1105,6 +1075,18 @@ namespace AZStd
return (pair_iter_bool(insertPos, true)); // return iterator for new element
}
template <typename ComparableToKey, typename... Args>
pair_iter_bool try_emplace_transparent(ComparableToKey&& key, Args&&... arguments);
template <typename ComparableToKey, typename... Args>
iterator try_emplace_transparent(const_iterator hint, ComparableToKey&& key, Args&&... arguments);
template <typename ComparableToKey, typename MappedType>
pair_iter_bool insert_or_assign_transparent(ComparableToKey&& key, MappedType&& value);
template <typename ComparableToKey, typename MappedType>
iterator insert_or_assign_transparent(const_iterator hint, ComparableToKey&& key, MappedType&& value);
storage_type m_data;
key_eq m_keyEqual;
hasher m_hasher;
@@ -1202,7 +1184,97 @@ namespace AZStd
return m_data.m_list.unlink(removePos);
}
}
#endif // AZSTD_HASH_TABLE_H
#pragma once
template <class Traits>
template <typename ComparableToKey, typename... Args>
inline auto hash_table<Traits>::try_emplace_transparent(ComparableToKey&& key, Args&&... arguments) -> pair_iter_bool
{
// Check if the key has a corresponding node in the container
if (iterator findIter = find(key); findIter != m_data.m_list.end())
{
return { findIter, false };
}
size_type bucketIndex = bucket_from_hash(m_hasher(key));
auto& [numElements, bucketFrontIter] = m_data.buckets()[bucketIndex];
// advance to the end of the bucket and piecewise construct the arguments at before that iterator
iterator insertIter = AZStd::next(bucketFrontIter, numElements);
if (numElements == 0)
{
// No elements in the bucket
// emplace the element at the beginning of the list and update the bucket front iterator to point to it
insertIter = m_data.m_list.emplace(m_data.m_list.begin(), AZStd::piecewise_construct, AZStd::forward_as_tuple(AZStd::forward<ComparableToKey>(key)),
AZStd::forward_as_tuple(AZStd::forward<Args>(arguments)...));
bucketFrontIter = insertIter;
}
else
{
// The returned iterator from list::emplace is pointing at the newly inserted element
insertIter = m_data.m_list.emplace(insertIter, AZStd::piecewise_construct, AZStd::forward_as_tuple(AZStd::forward<ComparableToKey>(key)),
AZStd::forward_as_tuple(AZStd::forward<Args>(arguments)...));
}
// Update the number of elements in the bucket using the numElements reference variable
++numElements;
m_data.rehash_if_needed(this);
return { insertIter, true };
}
template <class Traits>
template <typename ComparableToKey, typename... Args>
inline auto hash_table<Traits>::try_emplace_transparent(const_iterator, ComparableToKey&& key, Args&&... arguments) -> iterator
{
return try_emplace_transparent(AZStd::forward<ComparableToKey>(key), AZStd::forward<Args>(arguments)...).first;
}
template <class Traits>
template <typename ComparableToKey, typename MappedType>
inline auto hash_table<Traits>::insert_or_assign_transparent(ComparableToKey&& key, MappedType&& value) -> pair_iter_bool
{
// Check if the key has a corresponding node in the container
if (iterator findIter = find(key); findIter != m_data.m_list.end())
{
// Update the mapped element if the key has been found
findIter->second = AZStd::forward<MappedType>(value);
return { findIter, false };
}
size_type bucketIndex = bucket_from_hash(m_hasher(key));
auto& [numElements, bucketFrontIter] = m_data.buckets()[bucketIndex];
// advance to the end of the bucket and piecewise construct the arguments at before that iterator
iterator insertIter = AZStd::next(bucketFrontIter, numElements);
if (numElements == 0)
{
// No elements in the bucket
// emplace the element at the beginning of the list and update the bucket front iterator to point to it
insertIter = m_data.m_list.emplace(m_data.m_list.begin(), AZStd::piecewise_construct, AZStd::forward_as_tuple(AZStd::forward<ComparableToKey>(key)),
AZStd::forward_as_tuple(AZStd::forward<MappedType>(value)));
bucketFrontIter = insertIter;
}
else
{
// The returned iterator from list::emplace is pointing at the newly inserted element
insertIter = m_data.m_list.emplace(insertIter, AZStd::piecewise_construct, AZStd::forward_as_tuple(AZStd::forward<ComparableToKey>(key)),
AZStd::forward_as_tuple(AZStd::forward<MappedType>(value)));
}
// Update the number of elements in the bucket using the numElements reference variable
++numElements;
m_data.rehash_if_needed(this);
return { insertIter, true };
}
template <class Traits>
template <typename ComparableToKey, typename MappedType>
inline auto hash_table<Traits>::insert_or_assign_transparent(const_iterator, ComparableToKey&& key, MappedType&& value) -> iterator
{
return insert_or_assign_transparent(AZStd::forward<ComparableToKey>(key), AZStd::forward<MappedType>(value)).first;
}
}
@@ -39,4 +39,32 @@ namespace AZStd
return init;
}
// ref: https://en.cppreference.com/w/cpp/algorithm/inner_product
template<class InputIt1, class InputIt2, class T>
constexpr T inner_product(InputIt1 first1, InputIt1 last1, InputIt2 first2, T init)
{
while (first1 != last1)
{
init = AZStd::move(init) + *first1 * *first2;
++first1;
++first2;
}
return init;
}
// ref: https://en.cppreference.com/w/cpp/algorithm/inner_product
template<class InputIt1, class InputIt2, class T, class BinaryOperation1, class BinaryOperation2>
constexpr T inner_product(InputIt1 first1, InputIt1 last1, InputIt2 first2, T init, BinaryOperation1 op1, BinaryOperation2 op2)
{
while (first1 != last1)
{
init = op1(AZStd::move(init), op2(*first1, *first2));
++first1;
++first2;
}
return init;
}
} // namespace AZStd
@@ -17,6 +17,9 @@
#include <AzCore/std/optional.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/Android/JNI/Internal/ClassName.h>
#include <AzCore/Android/Utils.h>
namespace AZ
{
namespace Utils
@@ -55,6 +58,12 @@ namespace AZ
return appRoot ? AZStd::make_optional<AZStd::fixed_string<MaxPathLength>>(appRoot) : AZStd::nullopt;
}
AZStd::optional<AZ::IO::FixedMaxPathString> GetDevWriteStoragePath()
{
const char* writeStorage = AZ::Android::Utils::GetAppPublicStoragePath();
return writeStorage ? AZStd::make_optional<AZ::IO::FixedMaxPathString>(writeStorage) : AZStd::nullopt;
}
AZStd::optional<AZStd::fixed_string<MaxPathLength>> ConvertToAbsolutePath(AZStd::string_view path)
{
AZStd::fixed_string<MaxPathLength> absolutePath;
@@ -11,6 +11,7 @@
*/
#include <AzCore/Utils/Utils.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/PlatformIncl.h>
#include <mach-o/dyld.h>
@@ -169,19 +169,20 @@ namespace Platform
{
if (s.st_mode & S_IWUSR)
{
// file is already writeable
return true;
}
return chmod(sourceFileName, permissions | S_IWUSR) == 0;
}
else
{
if (s.st_mode & S_IRWXG)
if (s.st_mode & S_IWUSR)
{
return chmod(sourceFileName, permissions & ~(S_IWUSR)) == 0;
}
else
{
// file is already writeable
// file is already read-only
return true;
}
}
@@ -11,6 +11,7 @@
*/
#include <AzCore/Utils/Utils.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/PlatformIncl.h>
#include <AzCore/std/optional.h>
#include <AzCore/std/string/fixed_string.h>
@@ -51,6 +52,11 @@ namespace AZ
return AZStd::nullopt;
}
AZStd::optional<AZ::IO::FixedMaxPathString> GetDevWriteStoragePath()
{
return AZStd::nullopt;
}
AZStd::optional<AZStd::fixed_string<MaxPathLength>> ConvertToAbsolutePath(AZStd::string_view path)
{
AZStd::fixed_string<MaxPathLength> absolutePath;
@@ -11,6 +11,7 @@
*/
#include <AzCore/Utils/Utils.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/optional.h>
#include <AzCore/std/string/fixed_string.h>
@@ -48,5 +49,10 @@ namespace AZ
{
return AZStd::nullopt;
}
AZStd::optional<AZ::IO::FixedMaxPathString> GetDevWriteStoragePath()
{
return AZStd::nullopt;
}
}
}
@@ -20,4 +20,9 @@ namespace AZ::Utils
{
return AZStd::nullopt;
}
AZStd::optional<AZ::IO::FixedMaxPathString> GetDevWriteStoragePath()
{
return AZStd::nullopt;
}
}
@@ -24,4 +24,29 @@ namespace AZ::Utils
const char* pathToResources = [[[NSBundle mainBundle] resourcePath] UTF8String];
return AZStd::fixed_string<MaxPathLength>::format("%s/assets", pathToResources);
}
AZStd::optional<AZ::IO::FixedMaxPathString> GetDevWriteStoragePath()
{
NSArray* appSupportDirectoryPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
if ([appSupportDirectoryPaths count] == 0)
{
return AZStd::nullopt;
}
NSString* appSupportDir = static_cast<NSString*>([appSupportDirectoryPaths objectAtIndex:0]);
if (!appSupportDir)
{
return AZStd::nullopt;
}
const char* src = [appSupportDir UTF8String];
const size_t srcLen = strlen(src);
if (srcLen > MaxPathLength - 1)
{
return AZStd::nullopt;
}
return AZStd::make_optional<AZ::IO::FixedMaxPathString>(src);
}
}
@@ -1600,6 +1600,148 @@ namespace UnitTest
EXPECT_TRUE(test_adl.empty());
}
TEST_F(HashedContainers, UnorderedMapTryEmplace_DoesNotConstruct_OnExistingKey)
{
static int s_tryEmplaceConstructorCallCount;
s_tryEmplaceConstructorCallCount = 0;
struct TryEmplaceConstructorCalls
{
TryEmplaceConstructorCalls()
{
++s_tryEmplaceConstructorCallCount;
}
TryEmplaceConstructorCalls(int value)
: m_value{ value }
{
++s_tryEmplaceConstructorCallCount;
}
TryEmplaceConstructorCalls(const TryEmplaceConstructorCalls&)
{
++s_tryEmplaceConstructorCallCount;
}
int m_value{};
};
using TryEmplaceTestMap = AZStd::unordered_map<int, TryEmplaceConstructorCalls>;
TryEmplaceTestMap testContainer;
// try_emplace move key
AZStd::pair<TryEmplaceTestMap::iterator, bool> emplacePairIter = testContainer.try_emplace(1, 5);
EXPECT_EQ(1, s_tryEmplaceConstructorCallCount);
EXPECT_TRUE(emplacePairIter.second);
EXPECT_EQ(5, emplacePairIter.first->second.m_value);
// try_emplace copy key
int testKey = 3;
emplacePairIter = testContainer.try_emplace(testKey, 72);
EXPECT_EQ(2, s_tryEmplaceConstructorCallCount);
EXPECT_TRUE(emplacePairIter.second);
EXPECT_EQ(72, emplacePairIter.first->second.m_value);
// invoke try_emplace with hint and move key
TryEmplaceTestMap::iterator emplaceIter = testContainer.try_emplace(testContainer.end(), 5, 4092);
EXPECT_EQ(3, s_tryEmplaceConstructorCallCount);
EXPECT_EQ(4092, emplaceIter->second.m_value);
// invoke try_emplace with hint and copy key
testKey = 48;
emplaceIter = testContainer.try_emplace(testContainer.end(), testKey, 824);
EXPECT_EQ(4, s_tryEmplaceConstructorCallCount);
EXPECT_EQ(824, emplaceIter->second.m_value);
// Since the key of '1' exist, nothing should be constructed
emplacePairIter = testContainer.try_emplace(1, -6354);
EXPECT_EQ(4, s_tryEmplaceConstructorCallCount);
EXPECT_FALSE(emplacePairIter.second);
EXPECT_EQ(5, emplacePairIter.first->second.m_value);
}
TEST_F(HashedContainers, UnorderedMapTryEmplace_DoesNotMoveValue_OnExistingKey)
{
AZStd::unordered_map<int, AZStd::unique_ptr<int>> testMap;
auto testPtr = AZStd::make_unique<int>(5);
auto [emplaceIter, inserted] = testMap.try_emplace(1, AZStd::move(testPtr));
EXPECT_TRUE(inserted);
EXPECT_EQ(nullptr, testPtr);
testPtr = AZStd::make_unique<int>(7000);
auto [emplaceIter2, inserted2] = testMap.try_emplace(1, AZStd::move(testPtr));
EXPECT_FALSE(inserted2);
ASSERT_NE(nullptr, testPtr);
EXPECT_EQ(7000, *testPtr);
}
TEST_F(HashedContainers, UnorderedMapInsertOrAssign_PerformsAssignment_OnExistingKey)
{
static int s_tryInsertOrAssignConstructorCalls;
static int s_tryInsertOrAssignAssignmentCalls;
s_tryInsertOrAssignConstructorCalls = 0;
s_tryInsertOrAssignAssignmentCalls = 0;
struct InsertOrAssignInitCalls
{
InsertOrAssignInitCalls()
{
++s_tryInsertOrAssignConstructorCalls;
}
InsertOrAssignInitCalls(int value)
: m_value{ value }
{
++s_tryInsertOrAssignConstructorCalls;
}
InsertOrAssignInitCalls(const InsertOrAssignInitCalls& other)
: m_value{ other.m_value }
{
++s_tryInsertOrAssignConstructorCalls;
}
InsertOrAssignInitCalls& operator=(int value)
{
m_value = value;
++s_tryInsertOrAssignAssignmentCalls;
return *this;
}
int m_value{};
};
using InsertOrAssignTestMap = AZStd::unordered_map<int, InsertOrAssignInitCalls>;
InsertOrAssignTestMap testContainer;
// insert_or_assign move key
AZStd::pair<InsertOrAssignTestMap::iterator, bool> insertOrAssignPairIter = testContainer.insert_or_assign(1, 5);
EXPECT_EQ(1, s_tryInsertOrAssignConstructorCalls);
EXPECT_EQ(0, s_tryInsertOrAssignAssignmentCalls);
EXPECT_TRUE(insertOrAssignPairIter.second);
EXPECT_EQ(5, insertOrAssignPairIter.first->second.m_value);
// insert_or_assign copy key
int testKey = 3;
insertOrAssignPairIter = testContainer.insert_or_assign(testKey, 72);
EXPECT_EQ(2, s_tryInsertOrAssignConstructorCalls);
EXPECT_EQ(0, s_tryInsertOrAssignAssignmentCalls);
EXPECT_TRUE(insertOrAssignPairIter.second);
EXPECT_EQ(72, insertOrAssignPairIter.first->second.m_value);
// invoke insert_or_assign with hint and move key
InsertOrAssignTestMap::iterator insertOrAssignIter = testContainer.insert_or_assign(testContainer.end(), 5, 4092);
EXPECT_EQ(3, s_tryInsertOrAssignConstructorCalls);
EXPECT_EQ(0, s_tryInsertOrAssignAssignmentCalls);
EXPECT_EQ(4092, insertOrAssignIter->second.m_value);
// invoke insert_or_assign with hint and copy key
testKey = 48;
insertOrAssignIter = testContainer.insert_or_assign(testContainer.end(), testKey, 824);
EXPECT_EQ(4, s_tryInsertOrAssignConstructorCalls);
EXPECT_EQ(0, s_tryInsertOrAssignAssignmentCalls);
EXPECT_EQ(824, insertOrAssignIter->second.m_value);
// Since the key of '1' exist, only an assignment should take place
insertOrAssignPairIter = testContainer.insert_or_assign(1, -6354);
EXPECT_EQ(4, s_tryInsertOrAssignConstructorCalls);
EXPECT_EQ(1, s_tryInsertOrAssignAssignmentCalls);
EXPECT_FALSE(insertOrAssignPairIter.second);
EXPECT_EQ(-6354, insertOrAssignPairIter.first->second.m_value);
}
template <typename ContainerType>
class HashedMapDifferentAllocatorFixture
: public AllocatorsFixture
+61 -17
View File
@@ -10,14 +10,13 @@
*
*/
#include <AzCore/std/numeric.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/numeric.h>
namespace UnitTest
{
class AccumulateFixture
: public AllocatorsTestFixture
class AccumulateFixture : public AllocatorsTestFixture
{
};
@@ -25,31 +24,76 @@ namespace UnitTest
{
using ::testing::Eq;
AZStd::vector<int> numbers{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
AZStd::vector<int> numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
const int total = AZStd::accumulate(AZStd::cbegin(numbers), AZStd::cend(numbers), 0);
EXPECT_THAT(total, Eq(55));
}
TEST_F(AccumulateFixture, AccumulateWithBinaryOperator)
{
using ::testing::Eq;
using ::testing::ElementsAre;
using ::testing::Eq;
const AZStd::vector<int> numbers{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
const AZStd::vector<int> evenNumbers =
AZStd::accumulate(
AZStd::cbegin(numbers), AZStd::cend(numbers), AZStd::vector<int>{},
[](AZStd::vector<int> acc, const int number)
const AZStd::vector<int> numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
const AZStd::vector<int> evenNumbers = AZStd::accumulate(
AZStd::cbegin(numbers), AZStd::cend(numbers), AZStd::vector<int>{}, [](AZStd::vector<int> acc, const int number) {
if (number % 2 == 0)
{
if (number % 2 == 0)
{
acc.push_back(number);
}
acc.push_back(number);
}
return acc;
});
return acc;
});
EXPECT_THAT(evenNumbers.size(), Eq(5));
EXPECT_THAT(evenNumbers, ElementsAre(2, 4, 6, 8, 10));
}
class InnerProductFixture : public AllocatorsTestFixture
{
};
TEST_F(InnerProductFixture, InnerProductWithoutBinaryOperator)
{
using ::testing::Eq;
AZStd::vector<int> numbers1{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
AZStd::vector<int> numbers2{2, 4, 6, 8, 10, 12, 14, 16, 18, 20};
const int total = AZStd::inner_product(AZStd::cbegin(numbers1), AZStd::cend(numbers1), AZStd::cbegin(numbers2), 0);
EXPECT_THAT(total, Eq(770));
}
TEST_F(InnerProductFixture, InnerProductWithBinaryOperator)
{
using ::testing::ElementsAre;
using ::testing::Eq;
const AZStd::vector<int> number_values{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
const AZStd::vector<AZStd::string> number_names{"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"};
struct NumberLabel
{
int m_value;
AZStd::string m_name;
};
const AZStd::vector<NumberLabel> numberLabels = AZStd::inner_product(
AZStd::cbegin(number_values), AZStd::cend(number_values), AZStd::cbegin(number_names), AZStd::vector<NumberLabel>{},
[](AZStd::vector<NumberLabel> acc, const NumberLabel& numberLabel) {
acc.push_back(numberLabel);
return acc;
},
[](const int value, const AZStd::string& label) {
return NumberLabel{value, label};
});
EXPECT_THAT(numberLabels.size(), Eq(10));
for (size_t i = 0; i < numberLabels.size(); ++i)
{
EXPECT_THAT(numberLabels[i].m_value, Eq(number_values[i]));
EXPECT_THAT(numberLabels[i].m_name, Eq(number_names[i]));
}
}
} // namespace UnitTest
@@ -1353,6 +1353,149 @@ namespace UnitTest
EXPECT_EQ(1, uniqueMap.count(4));
}
TEST_F(Tree_Map, MapTryEmplace_DoesNotConstruct_OnExistingKey)
{
static int s_tryEmplaceConstructorCallCount;
s_tryEmplaceConstructorCallCount = 0;
struct TryEmplaceConstructorCalls
{
TryEmplaceConstructorCalls()
{
++s_tryEmplaceConstructorCallCount;
}
TryEmplaceConstructorCalls(int value)
: m_value{ value }
{
++s_tryEmplaceConstructorCallCount;
}
TryEmplaceConstructorCalls(const TryEmplaceConstructorCalls& other)
: m_value{ other.m_value }
{
++s_tryEmplaceConstructorCallCount;
}
int m_value{};
};
using TryEmplaceTestMap = AZStd::map<int, TryEmplaceConstructorCalls>;
TryEmplaceTestMap testContainer;
// try_emplace move key
AZStd::pair<TryEmplaceTestMap::iterator, bool> emplacePairIter = testContainer.try_emplace(1, 5);
EXPECT_EQ(1, s_tryEmplaceConstructorCallCount);
EXPECT_TRUE(emplacePairIter.second);
EXPECT_EQ(5, emplacePairIter.first->second.m_value);
// try_emplace copy key
int testKey = 3;
emplacePairIter = testContainer.try_emplace(testKey, 72);
EXPECT_EQ(2, s_tryEmplaceConstructorCallCount);
EXPECT_TRUE(emplacePairIter.second);
EXPECT_EQ(72, emplacePairIter.first->second.m_value);
// invoke try_emplace with hint and move key
TryEmplaceTestMap::iterator emplaceIter = testContainer.try_emplace(testContainer.end(), 5, 4092);
EXPECT_EQ(3, s_tryEmplaceConstructorCallCount);
EXPECT_EQ(4092, emplaceIter->second.m_value);
// invoke try_emplace with hint and copy key
testKey = 48;
emplaceIter = testContainer.try_emplace(testContainer.end(), testKey, 824);
EXPECT_EQ(4, s_tryEmplaceConstructorCallCount);
EXPECT_EQ(824, emplaceIter->second.m_value);
// Since the key of '1' exist, nothing should be constructed
emplacePairIter = testContainer.try_emplace(1, -6354);
EXPECT_EQ(4, s_tryEmplaceConstructorCallCount);
EXPECT_FALSE(emplacePairIter.second);
EXPECT_EQ(5, emplacePairIter.first->second.m_value);
}
TEST_F(Tree_Map, MapTryEmplace_DoesNotMoveValue_OnExistingKey)
{
AZStd::unordered_map<int, AZStd::unique_ptr<int>> testMap;
auto testPtr = AZStd::make_unique<int>(5);
auto [emplaceIter, inserted] = testMap.try_emplace(1, AZStd::move(testPtr));
EXPECT_TRUE(inserted);
EXPECT_EQ(nullptr, testPtr);
testPtr = AZStd::make_unique<int>(7000);
auto [emplaceIter2, inserted2] = testMap.try_emplace(1, AZStd::move(testPtr));
EXPECT_FALSE(inserted2);
ASSERT_NE(nullptr, testPtr);
EXPECT_EQ(7000, *testPtr);
}
TEST_F(Tree_Map, MapInsertOrAssign_PerformsAssignment_OnExistingKey)
{
static int s_tryInsertOrAssignConstructorCalls;
static int s_tryInsertOrAssignAssignmentCalls;
s_tryInsertOrAssignConstructorCalls = 0;
s_tryInsertOrAssignAssignmentCalls = 0;
struct InsertOrAssignInitCalls
{
InsertOrAssignInitCalls()
{
++s_tryInsertOrAssignConstructorCalls;
}
InsertOrAssignInitCalls(int value)
: m_value{ value }
{
++s_tryInsertOrAssignConstructorCalls;
}
InsertOrAssignInitCalls(const InsertOrAssignInitCalls& other)
: m_value{ other.m_value }
{
++s_tryInsertOrAssignConstructorCalls;
}
InsertOrAssignInitCalls& operator=(int value)
{
m_value = value;
++s_tryInsertOrAssignAssignmentCalls;
return *this;
}
int m_value{};
};
using InsertOrAssignTestMap = AZStd::map<int, InsertOrAssignInitCalls>;
InsertOrAssignTestMap testContainer;
// insert_or_assign move key
AZStd::pair<InsertOrAssignTestMap::iterator, bool> insertOrAssignPairIter = testContainer.insert_or_assign(1, 5);
EXPECT_EQ(1, s_tryInsertOrAssignConstructorCalls);
EXPECT_EQ(0, s_tryInsertOrAssignAssignmentCalls);
EXPECT_TRUE(insertOrAssignPairIter.second);
EXPECT_EQ(5, insertOrAssignPairIter.first->second.m_value);
// insert_or_assign copy key
int testKey = 3;
insertOrAssignPairIter = testContainer.insert_or_assign(testKey, 72);
EXPECT_EQ(2, s_tryInsertOrAssignConstructorCalls);
EXPECT_EQ(0, s_tryInsertOrAssignAssignmentCalls);
EXPECT_TRUE(insertOrAssignPairIter.second);
EXPECT_EQ(72, insertOrAssignPairIter.first->second.m_value);
// invoke insert_or_assign with hint and move key
InsertOrAssignTestMap::iterator insertOrAssignIter = testContainer.insert_or_assign(testContainer.end(), 5, 4092);
EXPECT_EQ(3, s_tryInsertOrAssignConstructorCalls);
EXPECT_EQ(0, s_tryInsertOrAssignAssignmentCalls);
EXPECT_EQ(4092, insertOrAssignIter->second.m_value);
// invoke insert_or_assign with hint and copy key
testKey = 48;
insertOrAssignIter = testContainer.insert_or_assign(testContainer.end(), testKey, 824);
EXPECT_EQ(4, s_tryInsertOrAssignConstructorCalls);
EXPECT_EQ(0, s_tryInsertOrAssignAssignmentCalls);
EXPECT_EQ(824, insertOrAssignIter->second.m_value);
// Since the key of '1' exist, only an assignment should take place
insertOrAssignPairIter = testContainer.insert_or_assign(1, -6354);
EXPECT_EQ(4, s_tryInsertOrAssignConstructorCalls);
EXPECT_EQ(1, s_tryInsertOrAssignAssignmentCalls);
EXPECT_FALSE(insertOrAssignPairIter.second);
EXPECT_EQ(-6354, insertOrAssignPairIter.first->second.m_value);
}
template <typename ContainerType>
class TreeMapDifferentAllocatorFixture
: public AllocatorsFixture
@@ -646,7 +646,11 @@ namespace UnitTest
}
};
#if AZ_TRAIT_DISABLE_ASSET_JOB_PARALLEL_TESTS
TEST_F(Parallel_Thread, DISABLED_Test)
#else
TEST_F(Parallel_Thread, Test)
#endif // AZ_TRAIT_DISABLE_ASSET_JOB_PARALLEL_TESTS
{
run();
}
@@ -835,11 +835,11 @@ namespace UnitTest
m_testAssetManager->SetParallelDependentLoadingEnabled(true);
}
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
TEST_F(AssetJobsFloodTest, DISABLED_LoadTest_SameAsset_DifferentFilters)
#else
TEST_F(AssetJobsFloodTest, LoadTest_SameAsset_DifferentFilters)
#endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
{
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect();
@@ -1188,11 +1188,11 @@ namespace UnitTest
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusDisconnect();
}
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
TEST_F(AssetJobsFloodTest, DISABLED_AssetWithNoLoadReference_LoadDependencies_NoLoadNotLoaded)
#else
TEST_F(AssetJobsFloodTest, AssetWithNoLoadReference_LoadDependencies_NoLoadNotLoaded)
#endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
{
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect();
// Setup has already created/destroyed assets
@@ -1229,11 +1229,11 @@ namespace UnitTest
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusDisconnect();
}
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
TEST_F(AssetJobsFloodTest, DISABLED_AssetWithNoLoadReference_LoadContainerDependencies_LoadAllLoadsNoLoad)
#else
TEST_F(AssetJobsFloodTest, AssetWithNoLoadReference_LoadContainerDependencies_LoadAllLoadsNoLoad)
#endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
{
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect();
// Setup has already created/destroyed assets
@@ -1268,11 +1268,11 @@ namespace UnitTest
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusDisconnect();
}
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
TEST_F(AssetJobsFloodTest, DISABLED_AssetWithNoLoadReference_LoadDependencies_BehaviorObeyed)
#else
TEST_F(AssetJobsFloodTest, AssetWithNoLoadReference_LoadDependencies_BehaviorObeyed)
#endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS
{
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect();
// Setup has already created/destroyed assets
@@ -808,6 +808,13 @@ namespace UnitTest
EXPECT_NE(assets.find(MyAsset1Id), assets.end());
AssetManager::Instance().ResumeAssetRelease();
// Sleep to allow for the assets to release
int retryCount = 100;
while ((--retryCount>0) && assets.size() > 0)
{
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
}
EXPECT_EQ(assets.size(), 0);
}
@@ -517,6 +517,104 @@ namespace UnitTest
AZ_TEST_STOP_TRACE_SUPPRESSION(0);
}
TEST_F(BehaviorContextTestFixture, MethodWhichReturnsAzEvent_WithNoAzBehaviorAzEventDescription_FailsValidation)
{
using TestAzEvent = AZ::Event<float>;
auto TestMethodWhichReturnsAzEvent = [](TestAzEvent& testEvent) -> TestAzEvent&
{
return testEvent;
};
UnitTest::TestRunner::Instance().StartAssertTests();
// Test reflecting function which returns AZ::Event
m_behaviorContext.Method("TestMethodWhichReturnsAzEvent", TestMethodWhichReturnsAzEvent);
int numErrors = UnitTest::TestRunner::Instance().StopAssertTests();
EXPECT_EQ(1, numErrors);
}
TEST_F(BehaviorContextTestFixture, MethodWhichReturnsAzEvent_WithEmptyEventName_FailsValidation)
{
using TestAzEvent = AZ::Event<float>;
auto TestMethodWhichReturnsAzEvent = [](TestAzEvent& testEvent) -> TestAzEvent&
{
return testEvent;
};
// Test reflecting function which returns AZ::Event
AZ::BehaviorAzEventDescription behaviorEventDesc;
// m_eventName member is not set, validation should fail
behaviorEventDesc.m_parameterNames.push_back("Scale");
UnitTest::TestRunner::Instance().StartAssertTests();
m_behaviorContext.Method("TestMethodWhichReturnsAzEvent", TestMethodWhichReturnsAzEvent)
->Attribute(AZ::Script::Attributes::AzEventDescription, AZStd::move(behaviorEventDesc));
int numErrors = UnitTest::TestRunner::Instance().StopAssertTests();
EXPECT_EQ(1, numErrors);
}
TEST_F(BehaviorContextTestFixture, MethodWhichReturnsAzEvent_WithParameterNameWhichIsEmpty_FailsValidation)
{
using TestAzEvent = AZ::Event<float>;
auto TestMethodWhichReturnsAzEvent = [](TestAzEvent& testEvent) -> TestAzEvent&
{
return testEvent;
};
// Test reflecting function which returns AZ::Event
AZ::BehaviorAzEventDescription behaviorEventDesc;
behaviorEventDesc.m_eventName = "TestAzEvent";
behaviorEventDesc.m_parameterNames.push_back(""); // Parameter name is empty, validation should fail
UnitTest::TestRunner::Instance().StartAssertTests();
m_behaviorContext.Method("TestMethodWhichReturnsAzEvent", TestMethodWhichReturnsAzEvent)
->Attribute(AZ::Script::Attributes::AzEventDescription, AZStd::move(behaviorEventDesc));
int numErrors = UnitTest::TestRunner::Instance().StopAssertTests();
EXPECT_EQ(1, numErrors);
}
TEST_F(BehaviorContextTestFixture, MethodWhichReturnsAzEvent_WithMismatchNumberOfParameters_FailsValidation)
{
using TestAzEvent = AZ::Event<float>;
auto TestMethodWhichReturnsAzEvent = [](TestAzEvent& testEvent) -> TestAzEvent&
{
return testEvent;
};
// Test reflecting function which returns AZ::Event
AZ::BehaviorAzEventDescription behaviorEventDesc;
behaviorEventDesc.m_eventName = "TestAzEvent";
// The AZ Event accepts one parameters.
// Two parameter names are being added here
behaviorEventDesc.m_parameterNames.push_back("Scale");
behaviorEventDesc.m_parameterNames.push_back("Size");
UnitTest::TestRunner::Instance().StartAssertTests();
m_behaviorContext.Method("TestMethodWhichReturnsAzEvent", TestMethodWhichReturnsAzEvent)
->Attribute(AZ::Script::Attributes::AzEventDescription, AZStd::move(behaviorEventDesc));
int numErrors = UnitTest::TestRunner::Instance().StopAssertTests();
EXPECT_LE(1, numErrors);
}
TEST_F(BehaviorContextTestFixture, MethodWhichReturnsAzEvent_WithCompleteAzBehaviorAzEventDescriptionription_PassesValidation)
{
using TestAzEvent = AZ::Event<float>;
auto TestMethodWhichReturnsAzEvent = [](TestAzEvent& testEvent) -> TestAzEvent&
{
return testEvent;
};
// Test reflecting function which returns AZ::Event
AZ::BehaviorAzEventDescription behaviorEventDesc;
behaviorEventDesc.m_eventName = "TestAzEvent";
behaviorEventDesc.m_parameterNames.push_back("Scale");
UnitTest::TestRunner::Instance().StartAssertTests();
m_behaviorContext.Method("TestMethodWhichReturnsAzEvent", TestMethodWhichReturnsAzEvent)
->Attribute(AZ::Script::Attributes::AzEventDescription, AZStd::move(behaviorEventDesc));
int numErrors = UnitTest::TestRunner::Instance().StopAssertTests();
EXPECT_EQ(0, numErrors);
}
class ClassWithEnumClass
{
public:
@@ -1336,7 +1336,11 @@ namespace UnitTest
size_t m_numThreads;
};
#if AZ_TRAIT_DISABLE_FAILED_FRAMEPROFILER_TEST
TEST_F(FrameProfilerComponentTest, DISABLED_Test)
#else
TEST_F(FrameProfilerComponentTest, Test)
#endif
{
run();
}
@@ -0,0 +1,266 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <limits>
#include <AzCore/Debug/LocalFileEventLogger.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace AZ::Debug
{
class LocalFileEventLoggerTest
: public UnitTest::AllocatorsFixture
{
public:
inline static constexpr EventNameHash MessageId = EventNameHash("Message");
inline static constexpr const char* LogFileName = "TestLog.azel";
};
TEST_F(LocalFileEventLoggerTest, RecordEvent_SingleString_WrittenToLog)
{
constexpr const char* message = "Hello world";
LocalFileEventLogger realLogger;
AZ::Test::ScopedAutoTempDirectory tempDir;
auto logFilePath = tempDir.Resolve(LogFileName);
auto logger = Interface<IEventLogger>::Get();
ASSERT_NE(logger, nullptr);
realLogger.Start(logFilePath.c_str());
logger->RecordStringEvent(MessageId, message);
realLogger.Stop();
EventLogReader reader;
ASSERT_TRUE(reader.ReadLog(logFilePath.c_str()));
EXPECT_EQ(reader.GetEventName(), PrologEventHash);
ASSERT_TRUE(reader.Next());
EXPECT_EQ(reader.GetEventName(), MessageId);
EXPECT_STREQ(reader.GetString().data(), message);
EXPECT_FALSE(reader.Next());
}
TEST_F(LocalFileEventLoggerTest, RecordEvent_SeveralStrings_WrittenToLog)
{
constexpr const char* messages[] = {
"Hello world",
"And goodbye",
"It has been a long and strange journey"
};
LocalFileEventLogger realLogger;
AZ::Test::ScopedAutoTempDirectory tempDir;
auto logFilePath = tempDir.Resolve(LogFileName);
auto logger = Interface<IEventLogger>::Get();
ASSERT_NE(logger, nullptr);
realLogger.Start(logFilePath.c_str());
for (auto message : messages)
{
logger->RecordStringEvent(MessageId, message);
}
realLogger.Stop();
EventLogReader reader;
ASSERT_TRUE(reader.ReadLog(logFilePath.c_str()));
EXPECT_EQ(reader.GetEventName(), PrologEventHash);
for (auto message : messages)
{
ASSERT_TRUE(reader.Next());
EXPECT_EQ(reader.GetEventName(), MessageId);
EXPECT_STREQ(reader.GetString().data(), message);
}
EXPECT_FALSE(reader.Next());
}
TEST_F(LocalFileEventLoggerTest, RecordEvent_StringsFromMultipleThreads_WrittenToLog)
{
constexpr const char* messages[] = {
"Hello world",
"And goodbye"
};
constexpr size_t totalThreads = 4;
AZ::Test::ScopedAutoTempDirectory tempDir;
LocalFileEventLogger realLogger;
auto logFilePath = tempDir.Resolve(LogFileName);
realLogger.Start(logFilePath.c_str());
AZStd::atomic_bool startLogging = false;
AZStd::thread threads[totalThreads];
for (size_t threadIndex = 0; threadIndex < totalThreads; ++threadIndex)
{
threads[threadIndex] = AZStd::thread([&startLogging, &messages]()
{
while (!startLogging)
{
AZStd::this_thread::yield();
}
auto logger = Interface<IEventLogger>::Get();
ASSERT_NE(logger, nullptr);
for (auto message : messages)
{
logger->RecordStringEvent(MessageId, message);
}
});
}
startLogging = true;
for (size_t threadIndex = 0; threadIndex < totalThreads; ++threadIndex)
{
threads[threadIndex].join();
}
realLogger.Stop();
EventLogReader reader;
ASSERT_TRUE(reader.ReadLog(logFilePath.c_str()));
uint64_t threadIds[totalThreads]{ 0 };
for (size_t threadIndex = 0; threadIndex < totalThreads; ++threadIndex)
{
EXPECT_EQ(reader.GetEventName(), PrologEventHash);
threadIds[threadIndex] = reader.GetThreadId();
for (size_t otherThreadIndex = 0; otherThreadIndex < threadIndex; ++otherThreadIndex)
{
EXPECT_NE(threadIds[threadIndex], threadIds[otherThreadIndex]);
}
for (auto message : messages)
{
ASSERT_TRUE(reader.Next());
EXPECT_EQ(reader.GetEventName(), MessageId);
EXPECT_STREQ(reader.GetString().data(), message);
}
reader.Next();
}
}
TEST_F(LocalFileEventLoggerTest, RecordEvent_BufferGetsWrittenWhenFull_WrittenToLog)
{
constexpr EventNameHash largeBlockId = EventNameHash("Large block");
constexpr size_t largeBlockSizeOffset = 14;
constexpr size_t largeBlockSize = AZStd::numeric_limits<uint16_t>::max() - largeBlockSizeOffset;
struct LargeBlock
{
char m_block[largeBlockSize];
};
constexpr const char* message = "The message after the large block.";
LocalFileEventLogger realLogger;
AZ::Test::ScopedAutoTempDirectory tempDir;
auto logFilePath = tempDir.Resolve(LogFileName);
auto logger = Interface<IEventLogger>::Get();
ASSERT_NE(logger, nullptr);
realLogger.Start(logFilePath.c_str());
LargeBlock& block = logger->RecordEventBegin<LargeBlock>(largeBlockId);
logger->RecordEventEnd();
logger->RecordStringEvent(MessageId, message);
realLogger.Stop();
EventLogReader reader;
ASSERT_TRUE(reader.ReadLog(logFilePath.c_str()));
EXPECT_EQ(reader.GetEventName(), PrologEventHash);
ASSERT_TRUE(reader.Next());
EXPECT_EQ(reader.GetEventName(), largeBlockId);
EXPECT_EQ(reader.GetEventSize(), largeBlockSize);
ASSERT_TRUE(reader.Next());
EXPECT_EQ(reader.GetEventName(), PrologEventHash); // Another prolog as a new cache block started.
ASSERT_TRUE(reader.Next());
EXPECT_EQ(reader.GetEventName(), MessageId);
EXPECT_STREQ(reader.GetString().data(), message);
EXPECT_FALSE(reader.Next());
}
TEST_F(LocalFileEventLoggerTest, Flush_DuringMultipleRecords_FlushDoesNotDeadlock)
{
constexpr size_t totalThreads = 8;
constexpr size_t recordsPerThreadCount = 2000;
constexpr size_t recordsYieldCount = totalThreads * 1000;
constexpr const char* message = "This is a threaded message test.";
LocalFileEventLogger realLogger;
AZ::Test::ScopedAutoTempDirectory tempDir;
auto logFilePath = tempDir.Resolve(LogFileName);
realLogger.Start(logFilePath.c_str());
AZStd::atomic_int totalRecordsWritten = 0;
AZStd::atomic_bool startLogging = false;
AZStd::thread threads[totalThreads];
for (size_t threadIndex = 0; threadIndex < totalThreads; ++threadIndex)
{
threads[threadIndex] = AZStd::thread([&startLogging, &totalRecordsWritten, &message, recordsPerThreadCount]()
{
while (!startLogging)
{
AZStd::this_thread::yield();
}
auto logger = Interface<IEventLogger>::Get();
ASSERT_NE(logger, nullptr);
for (size_t recordCount = 0; recordCount < recordsPerThreadCount; ++recordCount)
{
logger->RecordStringEvent(MessageId, message);
++totalRecordsWritten;
}
});
}
startLogging = true;
while (totalRecordsWritten < recordsYieldCount)
{
AZStd::this_thread::yield();
}
realLogger.Flush();
for (size_t threadIndex = 0; threadIndex < totalThreads; ++threadIndex)
{
threads[threadIndex].join();
}
realLogger.Stop();
}
} // namespace AZ::Debug
@@ -496,6 +496,67 @@ namespace SettingsRegistryScriptUtilsTests
AZStd::visit(ExpectedValueVisitor, testParam.m_expectedValue);
}
TEST_P(SettingsRegistryBehaviorContextParamFixture, GetNotifyEvent_AllowsRegistrationOfAzEventHandler_Succeeds)
{
auto&& testParam = GetParam();
bool updateNotifySent{};
// Set the expected value within the SettingsRegistry
auto ExpectedValueVisitor = [this, jsonPath = testParam.m_jsonPointerPath, setMethodName = testParam.m_setMethodName,
&updateNotifySent](auto&& value)
{
using ValueType = AZStd::remove_cvref_t<decltype(value)>;
const auto classIter = m_behaviorContext->m_classes.find(SettingsRegistryScriptClassName);
ASSERT_NE(m_behaviorContext->m_classes.end(), classIter);
AZ::BehaviorClass* settingsRegistryInterfaceClass = classIter->second;
ASSERT_NE(nullptr, settingsRegistryInterfaceClass);
// Lookup the SettingsRegistry Proxy GetNotifyEvent
auto foundIt = settingsRegistryInterfaceClass->m_methods.find("GetNotifyEvent");
ASSERT_NE(settingsRegistryInterfaceClass->m_methods.end(), foundIt);
// Create local settings registry proxy object
AZ::SettingsRegistryScriptUtils::Internal::SettingsRegistryScriptProxy settingsRegistryObject(m_registry.get());
// Register a notification call back
AZ::SettingsRegistryScriptUtils::Internal::SettingsRegistryScriptProxy::ScriptNotifyEvent::Handler scriptNotifyHandler(
[&updateNotifySent, jsonPath](AZStd::string_view path)
{
if (path == jsonPath)
{
updateNotifySent = true;
}
});
AZ::SettingsRegistryScriptUtils::Internal::SettingsRegistryScriptProxy::ScriptNotifyEvent* scriptNotifyEvent{};
EXPECT_TRUE(foundIt->second->InvokeResult(scriptNotifyEvent, &settingsRegistryObject));
ASSERT_NE(nullptr, scriptNotifyEvent);
// connect the scriptNotifyHandler to the settings registry script proxy event
scriptNotifyHandler.Connect(*scriptNotifyEvent);
// Find Reflected SettingsRegistryInterface Set* Method
foundIt = settingsRegistryInterfaceClass->m_methods.find(setMethodName);
ASSERT_NE(settingsRegistryInterfaceClass->m_methods.end(), foundIt);
// Invoke Set* method
bool setResult{};
EXPECT_TRUE(foundIt->second->InvokeResult(setResult, &settingsRegistryObject, jsonPath, value));
EXPECT_TRUE(setResult);
// Check value set through the BehaviorContext against the Settings Registry instance
// SettingsRegistryInterface::Get() can store the string result in an AZStd::fixed_string/AZStd::string
// So the AZStd::string_view is mapped to an AZStd::fixed_string for the purpose of calling Get()
using GetValueType = AZStd::conditional_t<AZStd::is_same_v<AZStd::string_view, ValueType>,
AZ::SettingsRegistryInterface::FixedValueString, ValueType>;
GetValueType outputValue{};
EXPECT_TRUE(m_registry->Get(outputValue, jsonPath));
EXPECT_EQ(value, outputValue);
};
AZStd::visit(ExpectedValueVisitor, testParam.m_expectedValue);
EXPECT_TRUE(updateNotifySent);
}
INSTANTIATE_TEST_CASE_P(
SettingsRegistryBehaviorContextGetFunctions,
SettingsRegistryBehaviorContextParamFixture,
@@ -73,6 +73,7 @@ set(FILES
UUIDTests.cpp
XML.cpp
Debug/AssetTracking.cpp
Debug/LocalFileEventLoggerTests.cpp
Debug/Trace.cpp
Name/NameJsonSerializerTests.cpp
Name/NameTests.cpp
@@ -61,6 +61,7 @@
#include <AzFramework/Archive/ArchiveFileIO.h>
#include <AzFramework/Script/ScriptRemoteDebugging.h>
#include <AzFramework/Script/ScriptComponent.h>
#include <AzFramework/Spawnable/SpawnableSystemComponent.h>
#include <AzFramework/StreamingInstall/StreamingInstall.h>
#include <AzFramework/TargetManagement/TargetManagementComponent.h>
#include <AzFramework/Viewport/CameraState.h>
@@ -407,6 +408,7 @@ namespace AzFramework
azrtti_typeid<AzFramework::InputSystemComponent>(),
azrtti_typeid<AzFramework::DrillerNetworkAgentComponent>(),
azrtti_typeid<AzFramework::StreamingInstall::StreamingInstallSystemComponent>(),
azrtti_typeid<AzFramework::SpawnableSystemComponent>(),
AZ::Uuid("{624a7be2-3c7e-4119-aee2-1db2bdb6cc89}"), // ScriptDebugAgent
});
@@ -28,6 +28,7 @@
#include <AzFramework/Scene/SceneSystemComponent.h>
#include <AzFramework/Script/ScriptComponent.h>
#include <AzFramework/Script/ScriptRemoteDebugging.h>
#include <AzFramework/Spawnable/SpawnableSystemComponent.h>
#include <AzFramework/StreamingInstall/StreamingInstall.h>
#include <AzFramework/TargetManagement/TargetManagementComponent.h>
#include <AzFramework/Visibility/OctreeSystemComponent.h>
@@ -63,6 +64,7 @@ namespace AzFramework
AzFramework::AzFrameworkConfigurationSystemComponent::CreateDescriptor(),
AzFramework::OctreeSystemComponent::CreateDescriptor(),
AzFramework::SpawnableSystemComponent::CreateDescriptor(),
});
}
@@ -57,7 +57,7 @@ namespace AzFramework
{
Scene* scene = createSceneOutcome.GetValue();
bool success = false;
EntityContextId gameEntityContextId;
EntityContextId gameEntityContextId = EntityContextId::CreateNull();
GameEntityContextRequestBus::BroadcastResult(gameEntityContextId, &GameEntityContextRequests::GetGameEntityContextId);
if (!gameEntityContextId.IsNull())
@@ -12,6 +12,8 @@
#include <AzFramework/Components/NonUniformScaleComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Math/ToString.h>
#include <AzCore/Component/Entity.h>
namespace AzFramework
{
@@ -33,7 +35,6 @@ namespace AzFramework
void NonUniformScaleComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("SkyCloudService"));
incompatible.push_back(AZ_CRC_CE("DebugDrawObbService"));
incompatible.push_back(AZ_CRC_CE("DebugDrawService"));
incompatible.push_back(AZ_CRC_CE("EMotionFXActorService"));
@@ -49,8 +50,6 @@ namespace AzFramework
incompatible.push_back(AZ_CRC_CE("PhysXShapeColliderService"));
incompatible.push_back(AZ_CRC_CE("PhysXCharacterControllerService"));
incompatible.push_back(AZ_CRC_CE("PhysXRagdollService"));
incompatible.push_back(AZ_CRC_CE("TouchBendingPhysicsService"));
incompatible.push_back(AZ_CRC_CE("WaterVolumeService"));
incompatible.push_back(AZ_CRC_CE("WhiteBoxService"));
incompatible.push_back(AZ_CRC_CE("NavigationAreaService"));
incompatible.push_back(AZ_CRC_CE("GeometryService"));
@@ -58,12 +57,9 @@ namespace AzFramework
incompatible.push_back(AZ_CRC_CE("CompoundShapeService"));
incompatible.push_back(AZ_CRC_CE("CylinderShapeService"));
incompatible.push_back(AZ_CRC_CE("DiskShapeService"));
incompatible.push_back(AZ_CRC_CE("FixedVertexContainerService"));
incompatible.push_back(AZ_CRC_CE("PolygonPrismShapeService"));
incompatible.push_back(AZ_CRC_CE("SphereShapeService"));
incompatible.push_back(AZ_CRC_CE("SplineService"));
incompatible.push_back(AZ_CRC_CE("TubeShapeService"));
incompatible.push_back(AZ_CRC_CE("VariableVertexContainerService"));
}
void NonUniformScaleComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
@@ -88,7 +84,17 @@ namespace AzFramework
void NonUniformScaleComponent::SetScale(const AZ::Vector3& scale)
{
m_scale = scale;
if (scale.GetMinElement() >= AZ::MinNonUniformScale)
{
m_scale = scale;
}
else
{
AZ::Vector3 clampedScale = scale.GetMax(AZ::Vector3(AZ::MinNonUniformScale));
AZ_Warning("Non-uniform Scale Component", false, "SetScale value was clamped from %s to %s for entity %s",
AZ::ToString(scale).c_str(), AZ::ToString(clampedScale).c_str(), GetEntity()->GetName().c_str());
m_scale = clampedScale;
}
m_scaleChangedEvent.Signal(m_scale);
}
@@ -17,25 +17,23 @@
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Quaternion.h>
#include <AzFramework/Network/NetBindingHandlerBus.h>
#include <AzFramework/Network/NetBindingSystemBus.h>
#include <AzFramework/Network/NetworkContext.h>
#include <GridMate/Replica/ReplicaChunk.h>
#include <GridMate/Replica/ReplicaFunctions.h>
#include <GridMate/Replica/DataSet.h>
#include <GridMate/Serialize/CompressionMarshal.h>
namespace AZ
{
class BehaviorTransformNotificationBusHandler : public TransformNotificationBus::Handler, public AZ::BehaviorEBusHandler
class BehaviorTransformNotificationBusHandler
: public TransformNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
AZ_EBUS_BEHAVIOR_BINDER(BehaviorTransformNotificationBusHandler, "{9CEF4DAB-F359-4A3E-9856-7780281E0DAA}", AZ::SystemAllocator
, OnTransformChanged
, OnParentChanged
, OnChildAdded
, OnChildRemoved
AZ_EBUS_BEHAVIOR_BINDER
(
BehaviorTransformNotificationBusHandler,
"{9CEF4DAB-F359-4A3E-9856-7780281E0DAA}",
AZ::SystemAllocator,
OnTransformChanged,
OnParentChanged,
OnChildAdded,
OnChildRemoved
);
void OnTransformChanged(const Transform& localTM, const Transform& worldTM) override
@@ -80,109 +78,10 @@ namespace AZ
new(self) TransformConfig();
}
}
} // namespace AZ
namespace AzFramework
{
//=========================================================================
// TransformReplicaChunk
// [3/9/2016]
//=========================================================================
class TransformReplicaChunk
: public GridMate::ReplicaChunkBase
{
public:
AZ_CLASS_ALLOCATOR(TransformReplicaChunk, AZ::SystemAllocator, 0);
static const char* GetChunkName() { return "TransformReplicaChunk"; }
TransformReplicaChunk()
: m_parentId("ParentId")
, m_localTranslation("LocalTranslationData")
, m_localRotation("LocalRotationData")
, m_localScale("LocalScaleData")
{
m_localTranslation.GetThrottler().SetThreshold(AZ::Vector3(0.005f, 0.005f, 0.005f));
m_localScale.GetThrottler().SetThreshold(AZ::Vector3(0.001f, 0.001f, 0.001f));
}
bool IsReplicaMigratable() override
{
return true;
}
void SetInitialTM(const AZ::Transform& t)
{
m_initialWorldTM = t;
}
void SetLocalTM(const AZ::Transform& t)
{
m_localScale.Set(t.GetScale());
m_localTranslation.Set(t.GetTranslation());
m_localRotation.Set(t.GetRotation());
}
AZ::Transform GetLocalTransform() const
{
AZ::Transform newXform;
newXform.SetTranslation(m_localTranslation.Get());
newXform.SetRotation(m_localRotation.Get());
newXform.SetScale(m_localScale.Get());
return newXform;
}
unsigned int GetLocalTime()
{
return GetReplicaManager()->GetTime().m_localTime;
}
// parentId (can have no parent)
DataSet<AZ::u64>::BindInterface<TransformComponent, &TransformComponent::OnNewNetParentData> m_parentId;
// transform
DataSet<AZ::Vector3, GridMate::Marshaler<AZ::Vector3>, GridMate::EpsilonThrottle<AZ::Vector3>>::BindInterface<TransformComponent, &TransformComponent::OnNewPositionData> m_localTranslation;
DataSet<AZ::Quaternion, GridMate::Marshaler<AZ::Quaternion>, GridMate::BasicThrottle<AZ::Quaternion>>::BindInterface<TransformComponent, &TransformComponent::OnNewRotationData> m_localRotation;
DataSet<AZ::Vector3, GridMate::Marshaler<AZ::Vector3>, GridMate::EpsilonThrottle<AZ::Vector3>>::BindInterface<TransformComponent, &TransformComponent::OnNewScaleData> m_localScale;
AZ::Transform m_initialWorldTM;
class Descriptor
: public ExternalChunkDescriptor<TransformReplicaChunk>
{
public:
ReplicaChunkBase* CreateFromStream(UnmarshalContext& context) override
{
// Pre/Post construct allow DataSets and RPCs to bind to the chunk.
TransformReplicaChunk* transformChunk = aznew TransformReplicaChunk;
context.m_iBuf->Read(transformChunk->m_initialWorldTM);
return transformChunk;
}
void DiscardCtorStream(UnmarshalContext& context) override
{
AZ::Transform discard;
context.m_iBuf->Read(discard);
}
void MarshalCtorData(ReplicaChunkBase* chunk, WriteBuffer& wb) override
{
TransformReplicaChunk* transformChunk = static_cast<TransformReplicaChunk*>(chunk);
TransformComponent* transformComponent = static_cast<TransformComponent*>(transformChunk->GetHandler());
if (transformComponent)
{
wb.Write(transformComponent->GetWorldTM());
}
else
{
wb.Write(transformChunk->m_initialWorldTM);
}
}
};
};
//=========================================================================
bool TransformComponentVersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
{
if (classElement.GetVersion() < 3)
@@ -204,28 +103,10 @@ namespace AzFramework
// future re-additions of it won't remove it (as long as they bump the version number.)
classElement.RemoveElementByName(AZ_CRC("InterpolateScale", 0x9d00b831));
}
return true;
}
//=========================================================================
// TransformComponent
// [8/9/2013]
//=========================================================================
TransformComponent::TransformComponent()
: m_parentTM(nullptr)
, m_parentActive(false)
, m_onNewParentKeepWorldTM(true)
, m_parentActivationTransformMode(ParentActivationTransformMode::MaintainOriginalRelativeTransform)
, m_isStatic(false)
, m_interpolatePosition(AZ::InterpolationMode::NoInterpolation)
, m_interpolateRotation(AZ::InterpolationMode::NoInterpolation)
{
m_localTM = AZ::Transform::CreateIdentity();
m_worldTM = AZ::Transform::CreateIdentity();
}
TransformComponent::TransformComponent(const TransformComponent& copy)
: m_localTM(copy.m_localTM)
, m_worldTM(copy.m_worldTM)
@@ -235,89 +116,9 @@ namespace AzFramework
, m_notificationBus(nullptr)
, m_onNewParentKeepWorldTM(copy.m_onNewParentKeepWorldTM)
, m_parentActivationTransformMode(copy.m_parentActivationTransformMode)
, m_replicaChunk(nullptr)
, m_isStatic(copy.m_isStatic)
, m_interpolatePosition(copy.m_interpolatePosition)
, m_interpolateRotation(copy.m_interpolateRotation)
, m_netTargetTranslation()
, m_netTargetRotation()
, m_netTargetScale(copy.m_netTargetScale)
{
CreateSamples();
if (copy.m_netTargetTranslation)
{
m_netTargetTranslation->SetNewTarget(copy.m_netTargetTranslation->GetTargetValue(), copy.m_netTargetTranslation->GetTargetTimestamp());
}
if (copy.m_netTargetRotation)
{
m_netTargetRotation->SetNewTarget(copy.m_netTargetRotation->GetTargetValue(), copy.m_netTargetRotation->GetTargetTimestamp());
}
SetSyncEnabled(copy.m_isSyncEnabled);
}
void TransformComponent::CreateTranslationSample()
{
switch(m_interpolatePosition)
{
case AZ::InterpolationMode::LinearInterpolation:
m_netTargetTranslation = AZStd::make_unique<AZ::LinearlyInterpolatedSample<AZ::Vector3>>();
break;
case AZ::InterpolationMode::NoInterpolation:
default:
m_netTargetTranslation = AZStd::make_unique<AZ::UninterpolatedSample<AZ::Vector3>>();
break;
}
}
void TransformComponent::CreateRotationSample()
{
switch (m_interpolateRotation)
{
case AZ::InterpolationMode::LinearInterpolation:
m_netTargetRotation = AZStd::make_unique<AZ::LinearlyInterpolatedSample<AZ::Quaternion>>();
break;
case AZ::InterpolationMode::NoInterpolation:
default:
m_netTargetRotation = AZStd::make_unique<AZ::UninterpolatedSample<AZ::Quaternion>>();
break;
}
}
void TransformComponent::CreateSamples()
{
if (m_netTargetTranslation)
{
auto target = m_netTargetTranslation->GetTargetValue();
auto timeStamp = m_netTargetTranslation->GetTargetTimestamp();
CreateTranslationSample();
m_netTargetTranslation->SetNewTarget(target, timeStamp);
}
else
{
CreateTranslationSample();
}
if (m_netTargetRotation)
{
auto target = m_netTargetRotation->GetTargetValue();
auto timeStamp = m_netTargetRotation->GetTargetTimestamp();
CreateRotationSample();
m_netTargetRotation->SetNewTarget(target, timeStamp);
}
else
{
CreateRotationSample();
}
}
TransformComponent::~TransformComponent()
{
;
}
bool TransformComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
@@ -328,9 +129,6 @@ namespace AzFramework
m_worldTM = config->m_worldTransform;
m_parentId = config->m_parentId;
m_parentActivationTransformMode = config->m_parentActivationTransformMode;
SetSyncEnabled(config->m_netSyncEnabled);
m_interpolatePosition = config->m_interpolatePosition;
m_interpolateRotation = config->m_interpolateRotation;
m_isStatic = config->m_isStatic;
return true;
}
@@ -345,9 +143,6 @@ namespace AzFramework
config->m_worldTransform = m_worldTM;
config->m_parentId = m_parentId;
config->m_parentActivationTransformMode = m_parentActivationTransformMode;
config->m_netSyncEnabled = IsSyncEnabled();
config->m_interpolatePosition = m_interpolatePosition;
config->m_interpolateRotation = m_interpolateRotation;
config->m_isStatic = m_isStatic;
return true;
}
@@ -366,8 +161,11 @@ namespace AzFramework
void TransformComponent::Deactivate()
{
EBUS_EVENT_ID(m_parentId, AZ::TransformNotificationBus, OnChildRemoved, GetEntityId());
UnbindFromNetwork();
auto parentTransform = AZ::TransformBus::FindFirstHandler(m_parentId);
if (parentTransform)
{
parentTransform->NotifyChildChangedEvent(AZ::ChildChangeType::Removed, GetEntityId());
}
m_notificationBus = nullptr;
if (m_parentId.IsValid())
@@ -379,12 +177,31 @@ namespace AzFramework
AZ::TransformBus::Handler::BusDisconnect();
}
void TransformComponent::BindTransformChangedEventHandler(AZ::TransformChangedEvent::Handler& handler)
{
handler.Connect(m_transformChangedEvent);
}
void TransformComponent::BindParentChangedEventHandler(AZ::ParentChangedEvent::Handler& handler)
{
handler.Connect(m_parentChangedEvent);
}
void TransformComponent::BindChildChangedEventHandler(AZ::ChildChangedEvent::Handler& handler)
{
handler.Connect(m_childChangedEvent);
}
void TransformComponent::NotifyChildChangedEvent(AZ::ChildChangeType changeType, AZ::EntityId entityId)
{
m_childChangedEvent.Signal(changeType, entityId);
}
void TransformComponent::SetLocalTM(const AZ::Transform& tm)
{
if (AreMoveRequestsAllowed())
{
SetLocalTMImpl(tm);
UpdateReplicaChunk();
}
}
@@ -393,28 +210,17 @@ namespace AzFramework
if (AreMoveRequestsAllowed())
{
SetWorldTMImpl(tm);
UpdateReplicaChunk();
}
}
void TransformComponent::SetParent(AZ::EntityId id)
{
if (!IsNetworkControlled())
{
SetParentImpl(id, true);
UpdateReplicaChunk();
}
SetParentImpl(id, true);
}
void TransformComponent::SetParentRelative(AZ::EntityId id)
{
if (!IsNetworkControlled())
{
SetParentImpl(id, m_isStatic);
UpdateReplicaChunk();
}
SetParentImpl(id, m_isStatic);
}
void TransformComponent::SetWorldTranslation(const AZ::Vector3& newPosition)
@@ -860,267 +666,6 @@ namespace AzFramework
}
void TransformComponent::OnEntityActivated(const AZ::EntityId& parentEntityId)
{
OnEntityActivatedImpl(parentEntityId);
UpdateReplicaChunk();
}
void TransformComponent::OnEntityDeactivated(const AZ::EntityId& parentEntityId)
{
if (!IsNetworkControlled())
{
OnEntityDeactivateImpl(parentEntityId);
UpdateReplicaChunk();
}
else
{
// If this transform is network controlled, then the localTM is updated by the network,
// so update m_parentTM and compute worldTM instead.
AZ_Assert(parentEntityId == m_parentId, "We expect to receive notifications only from the current parent!");
m_parentTM = nullptr;
m_parentActive = false;
ComputeWorldTM();
}
}
GridMate::ReplicaChunkPtr TransformComponent::GetNetworkBinding()
{
TransformReplicaChunk* replicaChunk = GridMate::CreateReplicaChunk<TransformReplicaChunk>();
replicaChunk->SetHandler(this);
m_replicaChunk = replicaChunk;
UpdateReplicaChunk();
return m_replicaChunk;
}
void TransformComponent::SetNetworkBinding(GridMate::ReplicaChunkPtr replicaChunk)
{
AZ_Assert(m_replicaChunk == nullptr, "Being bound to two ReplicaChunks");
bool isTransformChunk = replicaChunk != nullptr;
AZ_Assert(isTransformChunk, "Being bound to invalid chunk type");
if (isTransformChunk)
{
replicaChunk->SetHandler(this);
m_replicaChunk = replicaChunk;
TransformReplicaChunk* transformReplicaChunk = static_cast<TransformReplicaChunk*>(m_replicaChunk.get());
m_parentId = AZ::EntityId(transformReplicaChunk->m_parentId.Get());
m_worldTM = transformReplicaChunk->m_initialWorldTM;
m_localTM = transformReplicaChunk->GetLocalTransform();
CreateSamples();
m_netTargetTranslation->SetNewTarget(
transformReplicaChunk->m_localTranslation.Get(),
transformReplicaChunk->m_localTranslation.GetLastUpdateTime());
m_netTargetRotation->SetNewTarget(
transformReplicaChunk->m_localRotation.Get(),
transformReplicaChunk->m_localRotation.GetLastUpdateTime());
m_netTargetScale = transformReplicaChunk->m_localScale.Get();
if (HasAnyInterpolation())
{
// only connect if interpolation was selected for either position or rotation
AZ::TickBus::Handler::BusConnect();
}
}
m_onNewParentKeepWorldTM = false;
}
void TransformComponent::UnbindFromNetwork()
{
if (HasAnyInterpolation())
{
AZ::TickBus::Handler::BusDisconnect();
}
if (m_replicaChunk)
{
m_replicaChunk->SetHandler(nullptr);
m_replicaChunk = nullptr;
}
}
void TransformComponent::OnNewNetTransformData(const AZ::Transform& transform, const GridMate::TimeContext& /*tc*/)
{
SetLocalTMImpl(transform);
}
void TransformComponent::OnNewNetParentData(const AZ::u64& parentId, const GridMate::TimeContext& /*tc*/)
{
SetParentImpl(AZ::EntityId(parentId), false);
}
bool TransformComponent::IsNetworkControlled() const
{
return m_replicaChunk && m_replicaChunk->GetReplica() && !m_replicaChunk->IsMaster();
}
bool TransformComponent::IsPositionInterpolated()
{
return m_interpolatePosition != AZ::InterpolationMode::NoInterpolation;
}
bool TransformComponent::IsRotationInterpolated()
{
return m_interpolateRotation != AZ::InterpolationMode::NoInterpolation;
}
bool TransformComponent::HasAnyInterpolation()
{
return IsPositionInterpolated() || IsRotationInterpolated();
}
void TransformComponent::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*currentTime*/)
{
if (GetEntity() && GetEntity()->GetState() == AZ::Entity::State::Active)
{
if (m_replicaChunk && m_replicaChunk->IsProxy())
{
const unsigned int localTime = m_replicaChunk->GetReplicaManager()->GetTime().m_localTime;
const AZ::Transform newXform = GetInterpolatedTransform(localTime);
SetLocalTMImpl(newXform);
}
}
}
AZ::Transform TransformComponent::GetInterpolatedTransform(unsigned localTime)
{
const AZ::Vector3 newTranslation = m_netTargetTranslation->GetInterpolatedValue(localTime);
const AZ::Quaternion newRotation = m_netTargetRotation->GetInterpolatedValue(localTime);
AZ::Transform newXform = AZ::Transform::CreateFromQuaternionAndTranslation(newRotation, newTranslation);
newXform.MultiplyByScale(m_netTargetScale);
return newXform;
}
void TransformComponent::OnNewPositionData(const AZ::Vector3& translation, const GridMate::TimeContext& tc)
{
m_netTargetTranslation->SetNewTarget(translation, tc.m_realTime);
if (!HasAnyInterpolation())
{
unsigned int localTime = m_replicaChunk->GetReplicaManager()->GetTime().m_localTime;
AZ::Transform newXform = GetInterpolatedTransform(localTime);
SetLocalTMImpl(newXform);
}
};
void TransformComponent::OnNewRotationData(const AZ::Quaternion& rotation, const GridMate::TimeContext& tc)
{
m_netTargetRotation->SetNewTarget(rotation, tc.m_realTime);
if (!HasAnyInterpolation())
{
unsigned int localTime = m_replicaChunk->GetReplicaManager()->GetTime().m_localTime;
AZ::Transform newXform = GetInterpolatedTransform(localTime);
SetLocalTMImpl(newXform);
}
}
void TransformComponent::OnNewScaleData(const AZ::Vector3& scale, const GridMate::TimeContext& /*tc*/)
{
// no interpolation of scale by design, very unlikely somebody needs it
m_netTargetScale = scale;
}
void TransformComponent::UpdateReplicaChunk()
{
if (!IsNetworkControlled() && m_replicaChunk)
{
TransformReplicaChunk* transformReplicaChunk = static_cast<TransformReplicaChunk*>(m_replicaChunk.get());
transformReplicaChunk->SetLocalTM(GetLocalTM());
transformReplicaChunk->m_parentId.Set(static_cast<AZ::u64>(GetParentId()));
}
}
void TransformComponent::SetParentImpl(AZ::EntityId parentId, bool isKeepWorldTM)
{
if (parentId == GetEntityId())
{
AZ_Warning("TransformComponent", false, "An entity can not be set as its own parent.");
return;
}
AZ::EntityId oldParent = m_parentId;
if (m_parentId.IsValid())
{
AZ::TransformNotificationBus::Handler::BusDisconnect();
AZ::TransformHierarchyInformationBus::Handler::BusDisconnect();
AZ::EntityBus::Handler::BusDisconnect();
m_parentActive = false;
}
m_parentId = parentId;
if (m_parentId.IsValid())
{
AZ::Entity* parentEntity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(parentEntity, &AZ::ComponentApplicationBus::Events::FindEntity, m_parentId);
m_parentActive = parentEntity && (parentEntity->GetState() == AZ::Entity::State::Active);
m_onNewParentKeepWorldTM = isKeepWorldTM;
AZ::TransformNotificationBus::Handler::BusConnect(m_parentId);
AZ::TransformHierarchyInformationBus::Handler::BusConnect(m_parentId);
AZ::EntityBus::Handler::BusConnect(m_parentId);
}
else
{
m_parentTM = nullptr;
if (isKeepWorldTM)
{
SetWorldTM(m_worldTM);
}
else
{
SetLocalTM(m_localTM);
}
if (oldParent.IsValid())
{
EBUS_EVENT_PTR(m_notificationBus, AZ::TransformNotificationBus, OnTransformChanged, m_localTM, m_worldTM);
}
}
EBUS_EVENT_PTR(m_notificationBus, AZ::TransformNotificationBus, OnParentChanged, oldParent, parentId);
if (oldParent != parentId) // Don't send removal notification while activating.
{
EBUS_EVENT_ID(oldParent, AZ::TransformNotificationBus, OnChildRemoved, GetEntityId());
}
EBUS_EVENT_ID(parentId, AZ::TransformNotificationBus, OnChildAdded, GetEntityId());
}
void TransformComponent::SetLocalTMImpl(const AZ::Transform& tm)
{
m_localTM = tm;
ComputeWorldTM(); // We can user dirty flags and compute it later on demand
}
void TransformComponent::SetWorldTMImpl(const AZ::Transform& tm)
{
m_worldTM = tm;
ComputeLocalTM(); // We can user dirty flags and compute it later on demand
}
void TransformComponent::OnTransformChangedImpl(const AZ::Transform& /*parentLocalTM*/, const AZ::Transform& parentWorldTM)
{
// Called when our parent transform changes
// Ignore the event until we've already derived our local transform.
if (m_parentTM)
{
m_worldTM = parentWorldTM * m_localTM;
EBUS_EVENT_PTR(m_notificationBus, AZ::TransformNotificationBus, OnTransformChanged, m_localTM, m_worldTM);
}
}
void TransformComponent::OnEntityActivatedImpl(const AZ::EntityId& parentEntityId)
{
AZ_Assert(parentEntityId == m_parentId, "We expect to receive notifications only from the current parent!");
@@ -1171,15 +716,109 @@ namespace AzFramework
}
}
void TransformComponent::OnEntityDeactivateImpl(const AZ::EntityId& parentEntityId)
void TransformComponent::OnEntityDeactivated([[maybe_unused]] const AZ::EntityId& parentEntityId)
{
(void)parentEntityId;
AZ_Assert(parentEntityId == m_parentId, "We expect to receive notifications only from the current parent!");
m_parentTM = nullptr;
m_parentActive = false;
ComputeLocalTM();
}
void TransformComponent::SetParentImpl(AZ::EntityId parentId, bool isKeepWorldTM)
{
if (parentId == GetEntityId())
{
AZ_Warning("TransformComponent", false, "An entity can not be set as its own parent.");
return;
}
AZ::EntityId oldParent = m_parentId;
if (m_parentId.IsValid())
{
AZ::TransformNotificationBus::Handler::BusDisconnect();
AZ::TransformHierarchyInformationBus::Handler::BusDisconnect();
AZ::EntityBus::Handler::BusDisconnect();
m_parentActive = false;
}
m_parentId = parentId;
if (m_parentId.IsValid())
{
AZ::Entity* parentEntity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(parentEntity, &AZ::ComponentApplicationBus::Events::FindEntity, m_parentId);
m_parentActive = parentEntity && (parentEntity->GetState() == AZ::Entity::State::Active);
m_onNewParentKeepWorldTM = isKeepWorldTM;
AZ::TransformNotificationBus::Handler::BusConnect(m_parentId);
AZ::TransformHierarchyInformationBus::Handler::BusConnect(m_parentId);
AZ::EntityBus::Handler::BusConnect(m_parentId);
}
else
{
m_parentTM = nullptr;
if (isKeepWorldTM)
{
SetWorldTM(m_worldTM);
}
else
{
SetLocalTM(m_localTM);
}
if (oldParent.IsValid())
{
EBUS_EVENT_PTR(m_notificationBus, AZ::TransformNotificationBus, OnTransformChanged, m_localTM, m_worldTM);
m_transformChangedEvent.Signal(m_localTM, m_worldTM);
}
}
EBUS_EVENT_PTR(m_notificationBus, AZ::TransformNotificationBus, OnParentChanged, oldParent, parentId);
m_parentChangedEvent.Signal(oldParent, parentId);
if (oldParent != parentId) // Don't send removal notification while activating.
{
EBUS_EVENT_ID(oldParent, AZ::TransformNotificationBus, OnChildRemoved, GetEntityId());
auto oldParentTransform = AZ::TransformBus::FindFirstHandler(oldParent);
if (oldParentTransform)
{
oldParentTransform->NotifyChildChangedEvent(AZ::ChildChangeType::Removed, GetEntityId());
}
}
EBUS_EVENT_ID(parentId, AZ::TransformNotificationBus, OnChildAdded, GetEntityId());
auto newParentTransform = AZ::TransformBus::FindFirstHandler(parentId);
if (newParentTransform)
{
newParentTransform->NotifyChildChangedEvent(AZ::ChildChangeType::Added, GetEntityId());
}
}
void TransformComponent::SetLocalTMImpl(const AZ::Transform& tm)
{
m_localTM = tm;
ComputeWorldTM(); // We can user dirty flags and compute it later on demand
}
void TransformComponent::SetWorldTMImpl(const AZ::Transform& tm)
{
m_worldTM = tm;
ComputeLocalTM(); // We can user dirty flags and compute it later on demand
}
void TransformComponent::OnTransformChangedImpl(const AZ::Transform& /*parentLocalTM*/, const AZ::Transform& parentWorldTM)
{
// Called when our parent transform changes
// Ignore the event until we've already derived our local transform.
if (m_parentTM)
{
m_worldTM = parentWorldTM * m_localTM;
EBUS_EVENT_PTR(m_notificationBus, AZ::TransformNotificationBus, OnTransformChanged, m_localTM, m_worldTM);
m_transformChangedEvent.Signal(m_localTM, m_worldTM);
}
}
void TransformComponent::ComputeLocalTM()
{
if (m_parentTM)
@@ -1192,6 +831,7 @@ namespace AzFramework
}
EBUS_EVENT_PTR(m_notificationBus, AZ::TransformNotificationBus, OnTransformChanged, m_localTM, m_worldTM);
m_transformChangedEvent.Signal(m_localTM, m_worldTM);
}
void TransformComponent::ComputeWorldTM()
@@ -1206,15 +846,11 @@ namespace AzFramework
}
EBUS_EVENT_PTR(m_notificationBus, AZ::TransformNotificationBus, OnTransformChanged, m_localTM, m_worldTM);
m_transformChangedEvent.Signal(m_localTM, m_worldTM);
}
bool TransformComponent::AreMoveRequestsAllowed() const
{
if (IsNetworkControlled())
{
return false;
}
// Don't allow static transform to be moved while entity is activated.
// But do allow a static transform to be moved when the entity is deactivated.
if (m_isStatic && m_entity && (m_entity->GetState() > AZ::Entity::State::Init))
@@ -1248,7 +884,7 @@ namespace AzFramework
}
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(reflection);
if(behaviorContext)
if (behaviorContext)
{
behaviorContext->EBus<AZ::TransformNotificationBus>("TransformNotificationBus")->
Handler<AZ::BehaviorTransformNotificationBusHandler>();
@@ -1396,26 +1032,8 @@ namespace AzFramework
->Property("parentActivationTransformMode",
[](AZ::TransformConfig* config) { return (int&)(config->m_parentActivationTransformMode); },
[](AZ::TransformConfig* config, const int& i) { config->m_parentActivationTransformMode = (AZ::TransformConfig::ParentActivationTransformMode)i; })
->Property("netSyncEnabled", BehaviorValueProperty(&AZ::TransformConfig::m_netSyncEnabled))
->Property("interpolatePosition",
[](AZ::TransformConfig* config) { return (int&)(config->m_interpolatePosition); },
[](AZ::TransformConfig* config, const int& i) { config->m_interpolatePosition = (AZ::InterpolationMode)i; })
->Property("interpolateRotation",
[](AZ::TransformConfig* config) { return (int&)(config->m_interpolateRotation); },
[](AZ::TransformConfig* config, const int& i) { config->m_interpolateRotation = (AZ::InterpolationMode)i; })
->Property("isStatic", BehaviorValueProperty(&AZ::TransformConfig::m_isStatic))
;
}
NetworkContext* netContext = azrtti_cast<NetworkContext*>(reflection);
if (netContext)
{
netContext->Class<TransformComponent>()
->Chunk<TransformReplicaChunk, TransformReplicaChunk::Descriptor>()
->Field("ParentId", &TransformReplicaChunk::m_parentId)
->Field("LocalTranslationData", &TransformReplicaChunk::m_localTranslation)
->Field("LocalRotationData", &TransformReplicaChunk::m_localRotation)
->Field("LocalScaleData", &TransformReplicaChunk::m_localScale);
}
}
} // namespace AZ
@@ -29,26 +29,20 @@ namespace AzToolsFramework
namespace AzFramework
{
class TransformReplicaChunk;
class GameEntityContextComponent;
/// @deprecated Use AZ::TransformConfig
using TransformComponentConfiguration = AZ::TransformConfig;
//! Fundamental component that describes the entity in 3D space.
//! It is net-bindable. Only local transform is synchronized, so when
//! parented, the parent must properly synchronize its transform as well.
class TransformComponent
: public AZ::Component
, public AZ::EntityBus::Handler
, public AZ::TransformBus::Handler
, public AZ::TransformNotificationBus::Handler
, public AZ::EntityBus::Handler
, public AZ::TickBus::Handler
, private AZ::TransformHierarchyInformationBus::Handler
, public NetBindable
{
friend class TransformReplicaChunk;
public:
AZ_COMPONENT(TransformComponent, AZ::TransformComponentTypeId, NetBindable, AZ::TransformInterface);
@@ -56,11 +50,15 @@ namespace AzFramework
using ParentActivationTransformMode = AZ::TransformConfig::ParentActivationTransformMode;
TransformComponent();
TransformComponent() = default;
TransformComponent(const TransformComponent& copy);
virtual ~TransformComponent();
~TransformComponent() override = default;
// TransformBus events (publicly accessible)
void BindTransformChangedEventHandler(AZ::TransformChangedEvent::Handler& handler) override;
void BindParentChangedEventHandler(AZ::ParentChangedEvent::Handler& handler) override;
void BindChildChangedEventHandler(AZ::ChildChangedEvent::Handler& handler) override;
void NotifyChildChangedEvent(AZ::ChildChangeType changeType, AZ::EntityId entityId) override;
//! Returns true if the tm was set to the local transform.
const AZ::Transform& GetLocalTM() override { return m_localTM; }
//! Returns true if the tm was set to the world transform.
@@ -115,8 +113,6 @@ namespace AzFramework
float GetLocalY() override;
float GetLocalZ() override;
bool IsPositionInterpolated() override;
// Rotation modifiers
void SetRotation(const AZ::Vector3& eulerAnglesRadian) override;
void SetRotationQuaternion(const AZ::Quaternion& quaternion) override;
@@ -148,8 +144,6 @@ namespace AzFramework
AZ::Vector3 GetLocalRotation() override;
AZ::Quaternion GetLocalRotationQuaternion() override;
bool IsRotationInterpolated() override;
// Scale Modifiers
void SetScale(const AZ::Vector3& scale) override;
void SetScaleX(float scaleX) override;
@@ -187,28 +181,6 @@ namespace AzFramework
void OnEntityDeactivated(const AZ::EntityId& parentEntityId) override;
//! @}
//! Methods implementing NetBindable.
//! @{
GridMate::ReplicaChunkPtr GetNetworkBinding() override;
void SetNetworkBinding(GridMate::ReplicaChunkPtr chunk) override;
void UnbindFromNetwork() override;
//! Called by the net chunk when new transform data arrives from the network.
void OnNewNetTransformData(const AZ::Transform& transform, const GridMate::TimeContext& tc);
//! Called by the net chunk when new parent id arrives from the network.
void OnNewNetParentData(const AZ::u64& parentId, const GridMate::TimeContext& tc);
//! Returns true if this instance is non-authoritative.
bool IsNetworkControlled() const;
//! Triggers an update of the chunk data. Should only be called on the authoritative instance.
void UpdateReplicaChunk();
//! @}
// AZ::TickBus
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
//////////////////////////////////////////////////////////////////////////
// Actual Implementation Functions
// They are protected so we can gate them when network-controlled
@@ -216,8 +188,6 @@ namespace AzFramework
void SetLocalTMImpl(const AZ::Transform& tm);
void SetWorldTMImpl(const AZ::Transform& tm);
void OnTransformChangedImpl(const AZ::Transform& parentLocalTM, const AZ::Transform& parentWorldTM);
void OnEntityActivatedImpl(const AZ::EntityId& parentEntityId);
void OnEntityDeactivateImpl(const AZ::EntityId& parentEntityId);
void ComputeLocalTM();
void ComputeWorldTM();
//////////////////////////////////////////////////////////////////////////
@@ -228,44 +198,31 @@ namespace AzFramework
// TransformHierarchyInformationBus
void GatherChildren(AZStd::vector<AZ::EntityId>& children) override;
//! Feedback from corresponding replica chunk.
//! @{
void OnNewPositionData(const AZ::Vector3&, const GridMate::TimeContext&);
void OnNewRotationData(const AZ::Quaternion&, const GridMate::TimeContext&);
void OnNewScaleData(const AZ::Vector3&, const GridMate::TimeContext&);
//! @}
/// \ref ComponentDescriptor::GetProvidedServices
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
/// \ref ComponentDescriptor::Reflect
static void Reflect(AZ::ReflectContext* reflection);
AZ::Transform m_localTM; ///< Local transform relative to parent transform (same as worldTM if no parent).
AZ::Transform m_worldTM; ///< World transform including parent transform (same as localTM if no parent).
AZ::EntityId m_parentId; ///< If valid, this transform is parented to m_parentId.
AZ::TransformInterface* m_parentTM; ///< Cached - pointer to parent transform, to avoid extra calls. Valid only when if it's present.
bool m_parentActive; ///< Keeps track of the state of the parent entity.
AZ::TransformNotificationBus::BusPtr m_notificationBus; ///< Cached bus pointer to the notification bus.
bool m_onNewParentKeepWorldTM; ///< If set, recompute localTM instead of worldTM when parent becomes active.
ParentActivationTransformMode m_parentActivationTransformMode;
GridMate::ReplicaChunkPtr m_replicaChunk;
bool m_isStatic; ///< If true, the transform is static and doesn't move while entity is active.
AZ::InterpolationMode m_interpolatePosition; ///< Interpolation mode for net-synced position updates.
AZ::InterpolationMode m_interpolateRotation; ///< Interpolation mode for net-synced rotation updates.
AZ::TransformChangedEvent m_transformChangedEvent; ///< Event used to signal when a transform changes.
AZ::ParentChangedEvent m_parentChangedEvent; ///< Event used to signal when a transforms parent changes.
AZ::ChildChangedEvent m_childChangedEvent; ///< Event used to signal when a transform has a child entity added or removed.
private:
AZ::Transform m_localTM = AZ::Transform::CreateIdentity(); ///< Local transform relative to parent transform (same as worldTM if no parent).
AZ::Transform m_worldTM = AZ::Transform::CreateIdentity(); ///< World transform including parent transform (same as localTM if no parent).
bool HasAnyInterpolation();
AZ::EntityId m_parentId; ///< If valid, this transform is parented to m_parentId.
AZ::TransformInterface* m_parentTM = nullptr; ///< Cached - pointer to parent transform, to avoid extra calls. Valid only when if it's present.
AZ::TransformNotificationBus::BusPtr m_notificationBus; ///< Cached bus pointer to the notification bus.
ParentActivationTransformMode m_parentActivationTransformMode = ParentActivationTransformMode::MaintainOriginalRelativeTransform;
bool m_parentActive = false; ///< Keeps track of the state of the parent entity.
bool m_onNewParentKeepWorldTM = true; ///< If set, recompute localTM instead of worldTM when parent becomes active.
bool m_isStatic = false; ///< If true, the transform is static and doesn't move while entity is active.
void CreateSamples();
void CreateTranslationSample();
void CreateRotationSample();
AZ::Transform GetInterpolatedTransform(unsigned int localTime);
AZStd::unique_ptr<AZ::Sample<AZ::Vector3>> m_netTargetTranslation;
AZStd::unique_ptr<AZ::Sample<AZ::Quaternion>> m_netTargetRotation;
AZ::Vector3 m_netTargetScale;
//! @deprecated
//! @{
AZ::InterpolationMode m_interpolatePosition = AZ::InterpolationMode::NoInterpolation;
AZ::InterpolationMode m_interpolateRotation = AZ::InterpolationMode::NoInterpolation;
//! @}
};
} // namespace AZ
@@ -0,0 +1,47 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Engine/Engine.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Utils/Utils.h>
namespace AzFramework
{
namespace Engine
{
AZ::IO::FixedMaxPath FindEngineRoot(AZStd::string_view searchPath)
{
// File to locate
const char engineRootMarker[] = "engine.json";
AZ::IO::FixedMaxPath currentSearchPath{searchPath};
if (currentSearchPath.empty())
{
char executablePath[AZ_MAX_PATH_LEN];
AZ::Utils::GetExecutablePathReturnType result = AZ::Utils::GetExecutablePath(executablePath, AZ_MAX_PATH_LEN);
currentSearchPath = executablePath;
}
do
{
currentSearchPath = currentSearchPath.ParentPath();
if (AZ::IO::SystemFile::Exists((currentSearchPath / engineRootMarker).c_str()))
{
return currentSearchPath;
}
} while (currentSearchPath.ParentPath() != currentSearchPath);
return {};
}
}
} // AzFramework
@@ -0,0 +1,25 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/string/string_view.h>
namespace AzFramework
{
namespace Engine
{
// Helper to attempt to locate the engine root by searching up the directory tree. If no search path is
// provided the current executable path is used
AZ::IO::FixedMaxPath FindEngineRoot(AZStd::string_view searchPath = {});
} // Engine
} // AzFramework
@@ -93,6 +93,8 @@ namespace AzFramework
virtual void SetEntitiesRemovedCallback(OnEntitiesRemovedCallback onEntitiesRemovedCallback) = 0;
virtual void SetValidateEntitiesCallback(ValidateEntitiesCallback validateEntitiesCallback) = 0;
bool m_shouldAssertForLegacySlicesUsage = false;
protected:
OnEntitiesAddedCallback m_entitiesAddedCallback;
OnEntitiesRemovedCallback m_entitiesRemovedCallback;
@@ -19,6 +19,19 @@ namespace AzFramework
using EntityContextId = AZ::Uuid;
using EntityList = AZStd::vector<AZ::Entity*>;
class EntityOwnershipService;
class EntityOwnershipServiceInterface
{
public:
AZ_RTTI(EntityOwnershipServiceInterface, "{6490E958-5DF5-45CF-9A25-D857DB0C67DB}");
EntityOwnershipServiceInterface() = default;
virtual ~EntityOwnershipServiceInterface() = default;
virtual AZStd::unique_ptr<EntityOwnershipService> CreateEntityOwnershipService() = 0;
};
class EntityOwnershipServiceNotifications
: public AZ::EBusTraits
{
@@ -1,288 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Quaternion.h>
namespace Physics
{
constexpr const char * const AZ_TOUCH_BENDING_WINDOW = "AzTouchBending";
// Bone orientation
//
// _ TOP Point Z+ up
// | | ^
// | | |
// |*| (0,0,0) X- <------|--------> X+
// | | |
// |_| BOTTOM Point |
// Z-
/// SpinePoint contains properties of mass, thickness, damping and stiffness
/// for the bone that will be made. The SpinePoint is the BOTTOM point
/// of a Bone.
struct SpinePoint
{
///mass in Kg.
float m_mass;
///If you imagine the Bone to be a cylinder, this is its radius in meters.
float m_thickness;
///A value from 0.0 to 1.0. 0.0 means no damping, lots of back and forth movement around its original pose.
///1.0 means maximum damping, the segment will quickly converge back to its original pose.
float m_damping;
///A value from 0.0 to 1.0. 0.0 means no stiffness, the segment will look like a sad willow,
///It would never return to its original pose.
float m_stiffness;
///Position is in Model Space.
AZ::Vector3 m_position;
};
struct Spine
{
///Index of parent spine. -1 if no parent.
int m_parentSpineIndex;
///Index of the point within the parent spine array of segments.
///-1 if no parent.
int m_parentPointIndex;
///Array of segments.
AZStd::vector<SpinePoint> m_points;
};
typedef void* SpineTreeIDType;
///SpineTree is an archetype. This is basically the AzFramework version
///of CStatObj.SSpine.
struct SpineTree
{
///Unique Identifier Of this SpineTree.
SpineTreeIDType m_spineTreeId;
///A SpineTree ALWAYS contains at least one spine.
AZStd::vector<Spine> m_spines;
///Helper method.
size_t CalculateTotalNumberOfBones() const
{
size_t numberOfBones = 0;
for (const Spine& spine : m_spines)
{
numberOfBones += spine.m_points.size() - 1;
}
return numberOfBones;
}
};
///The Engine side of Touch bending uses this as an opaque handle.
///Only the TouchBending Gem knows what's inside.
///This handle corresponds one-to-one with a unique Vegetation Render Node instance.
struct TouchBendingTriggerHandle;
///The Engine side of Touch bending uses this as an opaque handle.
///Only the TouchBending Gem knows what's inside.
///This handle corresponds one-to-one with a unique CStatObjFoliage instance.
struct TouchBendingSkeletonHandle;
///Used by TouchBending Gem to talk back with the Engine.
class ITouchBendingCallback
{
public:
ITouchBendingCallback() = default;
virtual ~ITouchBendingCallback() = default;
/** @brief Checks if a render node is within e_CullVegActivation radius from the camera
*
* @param privateData Pointer to the Render Node inside the Engine that represents
* the touch bendable entity. From the point of view of the TouchBending Gem this
* is an opaque pointer, but from the point of view of the engine this is a
* CVegetation render node.
* @returns Returns a non-zero SpineTreeIDType if the Render Node is within
* e_CullVegActivation radius from the center of the main camera.
* Otherwise returns zero.
*/
virtual SpineTreeIDType CheckDistanceToCamera(const void* privateData) = 0;
/** @brief Builds a SpineTree archetype object using its SpineTreeIDType.
*
* \p privateData is a CVegetation*
* \p spineTreeId is a CStatObj*
*
* @param privateData Pointer to the Render Node inside the Engine that represents
* the touch bendable entity.
* @param spineTreeId Spine Tree Archetype Identifier as given previously by the Engine.
* @param spineTreeOut Output SpineTree archetype object.
* @returns TRUE if such \p spineTreeId is valid and a SpineTree archetype was successfully built.
* Otherwise returns FALSE.
*/
virtual bool BuildSpineTree(const void* privateData, SpineTreeIDType spineTreeId, SpineTree& spineTreeOut) = 0;
/** TouchBending Gem calls this to notify the Engine that a unique PhysicalizedSkeleton instance was built
* on behalf of \p privateData.
*
* The Engine uses this event to build a CStatObjFoliage to keep track of active touch bendable objects.
* The Engine will keep CStatObjFoliage alive as long as it is touched or for a specific lifetime in seconds
* defined by the CVar e_FoliageBranchesTimeout.
*
* @param privateData Pointer to the Render Node inside the Engine that represents
* the touch bendable entity.
* @param skeletonHandle Opaque pointer that the CStatObjFoliage must keep a copy to. The engine
* should should use it later when calling *Skeleton*() named methods of the TouchBendingBus.
* @returns true if the CStatObjFoliage was created successfully. It may return false only for cases where the CStatObj
* was removed and CStatObjFoliage can only be created if CStatObj is not null.
*/
virtual bool OnPhysicalizedTouchBendingSkeleton(const void* privateData, TouchBendingSkeletonHandle* skeletonHandle) = 0;
}; //class ITouchBendingCallback
//Exact same memory format as QuatTS
//CStatObjFoliage::ComputeSkinningTransformations() uses:
//QuatTS.q[x,y,z] as TOP joint position.
//QuatTS.t[x,y,z] as BOTTOM joint position.
//QuatTS.s CStatObjFoliage::GetSkinningData() reads this value for the first bone of each spine
// as marker for valid data, if less than zero, the spine is skipped by the Skinning code.
// A bone has two joints, TOP and BOTTOM:
//
// _ TOP Z+ up
// | | ^
// | | |
// |*| (0,0,0) X- <------|--------> X+
// | | |
// |_| BOTTOM |
// Z-
struct JointPositions
{
float m_TopJointLocation[3]; //Equivalent to QuatTS.q.xyz (ijk)
float m_qw; //Equivalent to QuatTS.q.w
float m_BottomJointLocation[3]; //Equivalent to QuatTS.t
float m_hasNewData; //Equivalent to QuatTS.s (See description above about CStatObjFoliage::GetSkinningData()).
};
/**
* Replacement of CryPhysics Touch Bending simulation.
*/
class TouchBendingRequest
: public AZ::EBusTraits
{
public:
AZ_RTTI(TouchBendingRequest, "{4E9DE1BE-F0C7-47E7-B315-9302F62D044C}");
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
virtual ~TouchBendingRequest() = default;
/// If the EBUS implementation (aka TouchBending Gem) returns TRUE
/// all of the Physics simulation for Touch Bendable CVegetation is done
/// by the TouchBending Gem with PhysX. If it returns FALSE the engine
/// will default to CryPhysics.
virtual bool IsTouchBendingEnabled() const = 0;
/** @brief Creates a TouchBending Trigger with a simple trigger box.
*
* Initially a TouchBending Trigger is nothing more than a trigger volume. There's
* no skeleton, etc. It will serve as the trigger, that when touched, builds a unique physicalized
* skeleton with the same amount of bones as a SpineTree. Recall that SpineTree is an archetype.
* The TouchBending Gem Builds a TouchBendingSkeletonHandle based on a SpineTree when TouchBendingTriggerHandle is touched.
*
* When the User adds an object via the Vegetation panel of the "Terrain Tool" UI
* this method will be called by the engine.
*
* If the User has enabled the Dynamic Vegetation Gem this method can be called
* at runtime as CVegetation nodes appear within the Camera Frustum.
*
* @param worldTransform This transform includes the scale factor. It is the position of the root
* of the CVegetation node.
* @param worldAabb Axis Aligned Bounding Box in world coordinates of the CVegetation node.
* @param callback The engine gives this callback to the TouchBending Gem for further communication.
* @param callbackPrivateData The Engine gives this opaque handle to TouchBending Gem so the Gem it can properly address it
* address the right Render Node instance when using the \p callback.
* @returns An opaque handle of a TouchBending Trigger Instance created by the TouchBending Gem.
*/
virtual TouchBendingTriggerHandle* CreateTouchBendingTrigger(const AZ::Transform& worldTransform,
const AZ::Aabb&worldAabb, ITouchBendingCallback* callback, const void * callbackPrivateData) = 0;
/** @brief Used by the engine to notify TouchBending Gem about the visibility status of the physicalized skeleton.
*
* @param skeletonHandle Opaque pointer to the physicalized skeleton created by TouchBending Gem.
* @param isVisible if TRUE the engine finds out that the skeleton is visible. If FALSE the engine calculated
* that the skeleton is either totally outside of the Camera Frustum or its distance
* from the camera exceeds the CVAR e_CullVegActivation.
* @param skeletonBoneCountOut It is the responsibility of the TouchBending Gem to fill this out
* with the number of bones available for skinning.
* @param triggerTouchCountOut It is the responsibility of the TouchBending Gem to fill this out
* with the number of objects that are touching the touch bending trigger.
* @returns void
*/
virtual void SetTouchBendingSkeletonVisibility(Physics::TouchBendingSkeletonHandle* skeletonHandle,
bool isVisible, AZ::u32& skeletonBoneCountOut, AZ::u32& triggerTouchCountOut) = 0;
/** @brief The engine calls this when it is deleting the Render Node.
*
* When the User deletes an object via the Vegetation panel of the Rollup Bar (Legacy) UI
* this method will be called by the engine.
*
* If the User has enabled the Dynamic Vegetation Gem this method can be called
* at runtime as CVegetation nodes disappear from the Camera Frustum.
*
* @param handle Opaque handle of the TouchBending trigger instance as created by the
* TouchBending Gem.
* @returns void
*/
virtual void DeleteTouchBendingTrigger(TouchBendingTriggerHandle* handle) = 0;
/** @brief The engine calls this to destroy a physicalized skeleton.
*
* The touch bending trigger remains active.
* This means that in the future something may touch the trigger
* and the skeleton is created again.
*
* @param skeletonHandle Opaque handle of the TouchBending Skeleton as created by the
* TouchBending Gem. The skeleton will be removed from the Physics World.
* @returns
*/
virtual void DephysicalizeTouchBendingSkeleton(TouchBendingSkeletonHandle* skeletonHandle) = 0;
/** Reads the current position of the pair-of-joints per bone of the Skeleton into the \p jointPositions
* buffer.
*
* @param skeletonHandle Opaque handle of the physicalized skeleton instance as created by the
* TouchBending Gem.
* @param jointPositions Buffer where the Top and Bottom Joint positions for each bone
* is written to. Please read the documentation of "struct JointPositions" for clarification.
* @returns void
*/
virtual void ReadJointPositionsOfSkeleton(TouchBendingSkeletonHandle* skeletonHandle, JointPositions* jointPositions) = 0;
};
using TouchBendingBus = AZ::EBus<TouchBendingRequest>;
/// A helper method to test if there's a Gem implementing the TouchBendingBus.
AZ_INLINE bool IsTouchBendingEnabled()
{
bool isEnabled = false;
TouchBendingBus::BroadcastResult(isEnabled, &TouchBendingBus::Events::IsTouchBendingEnabled);
return isEnabled;
}
}
@@ -17,7 +17,7 @@
namespace AzFramework
{
static const char* PlatformNames[PlatformId::NumPlatformIds] = { PlatformPC, PlatformES3, PlatformIOS, PlatformOSX, PlatformXenia, PlatformProvo, PlatformSalem, PlatformJasper, PlatformServer, PlatformAll, PlatformAllClient };
static const char* PlatformNames[PlatformId::NumPlatformIds] = { PlatformPC, PlatformES3, PlatformIOS, PlatformOSX, PlatformProvo, PlatformSalem, PlatformJasper, PlatformServer, PlatformAll, PlatformAllClient };
const char* PlatformIdToPalFolder(AzFramework::PlatformId platform)
{
@@ -35,8 +35,6 @@ namespace AzFramework
return "iOS";
case AzFramework::OSX:
return "Mac";
case AzFramework::XENIA:
return "Xenia";
case AzFramework::PROVO:
return "Provo";
case AzFramework::SALEM:
@@ -84,10 +82,6 @@ namespace AzFramework
{
return PlatformSalem;
}
else if (osPlatform == PlatformCodeNameXenia)
{
return PlatformCodeNameXenia;
}
else if (osPlatform == PlatformCodeNameJasper)
{
return PlatformJasper;
@@ -220,9 +214,6 @@ namespace AzFramework
case PlatformId::OSX:
platformCodes.emplace_back(PlatformCodeNameMac);
break;
case PlatformId::XENIA:
platformCodes.emplace_back(PlatformCodeNameXenia);
break;
case PlatformId::PROVO:
platformCodes.emplace_back(PlatformCodeNameProvo);
break;
@@ -28,7 +28,6 @@ namespace AzFramework
constexpr char PlatformES3[] = "es3";
constexpr char PlatformIOS[] = "ios";
constexpr char PlatformOSX[] = "osx_gl";
constexpr char PlatformXenia[] = "xenia";
constexpr char PlatformProvo[] = "provo";
constexpr char PlatformSalem[] = "salem";
constexpr char PlatformJasper[] = "jasper";
@@ -39,7 +38,6 @@ namespace AzFramework
constexpr char PlatformCodeNameAndroid[] = "Android";
constexpr char PlatformCodeNameiOS[] = "iOS";
constexpr char PlatformCodeNameMac[] = "Mac";
constexpr char PlatformCodeNameXenia[] = "Xenia";
constexpr char PlatformCodeNameProvo[] = "Provo";
constexpr char PlatformCodeNameSalem[] = "Salem";
constexpr char PlatformCodeNameJasper[] = "Jasper";
@@ -57,7 +55,6 @@ namespace AzFramework
ES3,
IOS,
OSX,
XENIA,
PROVO,
SALEM,
JASPER,
@@ -68,7 +65,7 @@ namespace AzFramework
// Add new platforms above this
NumPlatformIds
);
constexpr int NumClientPlatforms = 8;
constexpr int NumClientPlatforms = 7;
constexpr int NumPlatforms = NumClientPlatforms + 1; // 1 "Server" platform currently
enum class PlatformFlags : AZ::u32
{
@@ -77,7 +74,6 @@ namespace AzFramework
Platform_ES3 = 1 << PlatformId::ES3,
Platform_IOS = 1 << PlatformId::IOS,
Platform_OSX = 1 << PlatformId::OSX,
Platform_XENIA = 1 << PlatformId::XENIA,
Platform_PROVO = 1 << PlatformId::PROVO,
Platform_SALEM = 1 << PlatformId::SALEM,
Platform_JASPER = 1 << PlatformId::JASPER,
@@ -89,7 +85,7 @@ namespace AzFramework
// A special platform that will always correspond to all non-server platforms, even if new ones are added
Platform_ALL_CLIENT = 1ULL << 31,
AllNamedPlatforms = Platform_PC | Platform_ES3 | Platform_IOS | Platform_OSX | Platform_XENIA | Platform_PROVO | Platform_SALEM | Platform_JASPER | Platform_SERVER,
AllNamedPlatforms = Platform_PC | Platform_ES3 | Platform_IOS | Platform_OSX | Platform_PROVO | Platform_SALEM | Platform_JASPER | Platform_SERVER,
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(PlatformFlags);
@@ -0,0 +1,224 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/ProjectManager/ProjectManager.h>
#include <AzFramework/Engine/Engine.h>
#include <AzCore/IO/SystemFile.h>
namespace AzFramework
{
namespace ProjectManager
{
// Check if any project path appears to have been provided on the command line
bool HasCommandLineProjectName(const int argc, char* argv[])
{
constexpr int numOptionPrefixes = 3;
static const char* optionPrefixes[numOptionPrefixes] = { "/", "--", "-" };
constexpr int numOptionNames = 2;
static const char* optionNames[numOptionNames] = { "projectpath", R"(regset="/Amazon/AzCore/Bootstrap/sys_game_folder)" };
for (int i = 1; i < argc; ++i)
{
int thisPrefix = 0;
for (; thisPrefix < numOptionPrefixes; ++thisPrefix)
{
if (strncmp(argv[i], optionPrefixes[thisPrefix], strlen(optionPrefixes[thisPrefix])) == 0)
{
break;
}
}
// If the argument doesn't start with any of our switch start parameters, this isn't an argument giving us a project
if (thisPrefix == numOptionPrefixes)
{
continue;
}
// We compare the portion of the string after our prefix
int startIndex = strlen(optionPrefixes[thisPrefix]);
// If the whole argument was just one of the prefixes, this also isn't what we were looking for
if (startIndex == strlen(argv[i]))
{
continue;
}
int switchNum = 0;
for (; switchNum < numOptionNames; ++switchNum)
{
// Start the string comparison at startIndex for each string - after the option indicator
if (azstrnicmp(&argv[i][startIndex], optionNames[switchNum], strlen(optionNames[switchNum])) == 0)
{
int expectedOptionLength = strlen(optionNames[switchNum]) + startIndex;
// The option is what we're looking for if it had a space after it (it was the whole argument) or it has an equals next
if (strlen(argv[i]) == (expectedOptionLength) || ((strlen(argv[i]) > expectedOptionLength ) && argv[i][expectedOptionLength] == '='))
{
// We found one of the acceptable arguments
return true;
}
}
}
}
return false;
}
// Check for a project name, if not found, attempt to launch project manager and shut down
ProjectPathCheckResult CheckProjectPathProvided(const int argc, char* argv[])
{
// If we were able to locate a path to a project, we're done
if (HasProjectName(argc, argv))
{
return ProjectPathCheckResult::ProjectPathFound;
}
if (LaunchProjectManager())
{
AZ_TracePrintf("ProjectManager", "Project Manager launched successfully, requesting exit.");
return ProjectPathCheckResult::ProjectManagerLaunched;
}
AZ_Error("ProjectManager", false, "Project Manager failed to launch and no project selected!");
return ProjectPathCheckResult::ProjectManagerLaunchFailed;
}
} // ProjectManager
bool ProjectManager::HasProjectName(const int argc, char* argv[])
{
return HasCommandLineProjectName(argc, argv) || HasBootstrapProjectName();
}
// This is a transition method until all projects/tests/etc have been converted to expect and use the projectpath parameter
// If bootstrap.cfg exists and has a valid project name we should assume we're launching using it
// After that time it can be removed
bool ProjectManager::HasBootstrapProjectName(AZStd::string_view projectFolder)
{
AZ::IO::FixedMaxPath enginePath = Engine::FindEngineRoot(projectFolder);
if (enginePath.empty())
{
AZ_Warning("ProjectManager", false, "Couldn't find engine root");
return false;
}
auto bootstrapPath = enginePath / "bootstrap.cfg";
if (!AZ::IO::SystemFile::Exists(bootstrapPath.c_str()))
{
AZ_Warning("ProjectManager", false, "No bootstrap file found at %s", bootstrapPath.c_str());
return false;
}
AZStd::fixed_string< MaxBootstrapFileSize> bootstrapString;
auto fileSize = AZ::IO::SystemFile::Length(bootstrapPath.c_str());
if (fileSize >= MaxBootstrapFileSize)
{
AZ_Warning("ProjectManager", false, "Bootstrap read size at %s is %zu", bootstrapPath.c_str(), fileSize);
bootstrapString.resize_no_construct(MaxBootstrapFileSize);
}
else
{
bootstrapString.resize_no_construct(fileSize);
}
AZ::IO::SystemFile::SizeType bytesRead = AZ::IO::SystemFile::Read(bootstrapPath.c_str(), bootstrapString.data(), MaxBootstrapFileSize - 1);
if (bytesRead == (MaxBootstrapFileSize - 1))
{
AZ_Warning("ProjectManager", false, "Bootstrap read size at %s was %zu", bootstrapPath.c_str(), bytesRead);
}
if (!ContentHasProjectName(bootstrapString))
{
AZ_TracePrintf("ProjectManager", "Bootstrap at %s did not contain project name", bootstrapPath.c_str());
return false;
}
return true;
}
// This is a transition method until all projects/tests/etc have been converted to expect and use the projectpath parameter
// If bootstrap.cfg exists and has a valid project name we should assume we're launching using it
// After that time it can be removed
bool ProjectManager::ContentHasProjectName(AZStd::fixed_string< MaxBootstrapFileSize>& bootstrapString)
{
static const char* const projectKey = "sys_game_folder";
size_t searchStart = bootstrapString.find(projectKey);
while (searchStart != bootstrapString.npos)
{
// Once we've found the key we need to search the line forward and backwards. Commented out lines shouldn't count
// and if there's no value after the equals then it's also not set
auto checkPos = searchStart;
// We're at the start already if this is position 0
bool foundLineStart = checkPos == 0;
if (checkPos)
{
--checkPos;
}
while (checkPos > 0 && bootstrapString[checkPos] != '-')
{
if (bootstrapString[checkPos] == '\n')
{
// Looks like a valid key
foundLineStart = true;
break;
}
if (!std::isspace(bootstrapString[checkPos]))
{
// This appears to be some other character appearing before our key, this isn't valid
break;
}
--checkPos;
}
if (!foundLineStart)
{
// Commented line or other content preceding our key, keep searching
searchStart = bootstrapString.find(projectKey, searchStart + 1);
continue;
}
checkPos = searchStart + strlen(projectKey);
bool foundEquals = false;
while (checkPos < bootstrapString.length())
{
if (bootstrapString[checkPos] == '\n')
{
// We've reached the end of the line and didn't find anything that seems to be a value for our key
break;
}
if (std::isspace(bootstrapString[checkPos]))
{
// Whitespace - keep searching back
++checkPos;
continue;
}
if (bootstrapString[checkPos] == '=')
{
foundEquals = true;
++checkPos;
continue;
}
if (foundEquals)
{
auto nameEnd = bootstrapString.find_first_of(" \n", checkPos);
if (nameEnd == bootstrapString.npos)
{
// End of content, this is valid
nameEnd = bootstrapString.length();
}
constexpr size_t nameMax = 100;
if (nameEnd - checkPos > nameMax)
{
AZ_Warning("ProjectManager", false, "Project name exceeded %zu characters (%zu)", nameMax, nameEnd - checkPos);
return false;
}
AZStd::fixed_string<nameMax + 1> projectName(&bootstrapString[checkPos], nameEnd - checkPos);
AZ_TracePrintf("ProjectManager", "Found project name of %s", projectName.c_str());
// This is not a space, we've found our key, and we've found some sort of non space entry, we count this as "it looks like we have a value entered"
return true;
}
// there was some other content on this line after our key before the equals that was not a space, this isn't our key
searchStart = bootstrapString.find(projectKey, searchStart + 1);
break;
}
searchStart = bootstrapString.find(projectKey, searchStart + 1);
}
return false;
}
} // AzFramework
@@ -0,0 +1,43 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/std/string/string_view.h>
namespace AzFramework
{
namespace ProjectManager
{
constexpr AZ::IO::SystemFile::SizeType MaxBootstrapFileSize = 1024 * 10;
// Check if any project name can be found anywhere
bool HasProjectName(const int argc, char* argv[]);
// Check if any project name can be found on the command line
bool HasCommandLineProjectName(const int argc, char* argv[]);
// Check if a relative project is being used through bootstrap
bool HasBootstrapProjectName(AZStd::string_view projectFolder = {});
// Search content for project name key
bool ContentHasProjectName(AZStd::fixed_string< MaxBootstrapFileSize>& bootstrapString);
enum class ProjectPathCheckResult
{
ProjectManagerLaunchFailed = -1,
ProjectManagerLaunched = 0,
ProjectPathFound = 1
};
// Check for a project name, if not found, attempts to launch project manager and returns false
ProjectPathCheckResult CheckProjectPathProvided(const int argc, char* argv[]);
// Attempt to Launch the project manager. Requires locating the engine root, project manager script, and python.
bool LaunchProjectManager();
}
} // AzFramework
@@ -0,0 +1,74 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Spawnable/Spawnable.h>
namespace AzFramework
{
Spawnable::Spawnable(const AZ::Data::AssetId& id)
: AZ::Data::AssetData(id)
{
}
Spawnable::Spawnable(Spawnable&& other)
: m_entities(AZStd::move(other.m_entities))
{
}
Spawnable& Spawnable::operator=(Spawnable&& other)
{
if (this != &other)
{
m_entities = AZStd::move(other.m_entities);
}
return *this;
}
const Spawnable::EntityList& Spawnable::GetEntities() const
{
return m_entities;
}
Spawnable::EntityList& Spawnable::GetEntities()
{
return m_entities;
}
bool Spawnable::IsEmpty() const
{
return m_entities.empty();
}
SpawnableMetaData& Spawnable::GetMetaData()
{
return m_metaData;
}
const SpawnableMetaData& Spawnable::GetMetaData() const
{
return m_metaData;
}
void Spawnable::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<Spawnable, AZ::Data::AssetData>()->Version(1)
->Field("Meta data", &Spawnable::m_metaData)
->Field("Entities", &Spawnable::m_entities);
}
}
} // namespace AzFramework
@@ -0,0 +1,65 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Spawnable/SpawnableMetaData.h>
namespace AzFramework
{
class ReflectContext;
class Spawnable final
: public AZ::Data::AssetData
{
public:
AZ_CLASS_ALLOCATOR(Spawnable, AZ::SystemAllocator, 0);
AZ_RTTI(AzFramework::Spawnable, "{855E3021-D305-4845-B284-20C3F7FDF16B}", AZ::Data::AssetData);
using EntityList = AZStd::vector<AZStd::unique_ptr<AZ::Entity>>;
inline static constexpr const char* FileExtension = "spawnable";
Spawnable() = default;
explicit Spawnable(const AZ::Data::AssetId& id);
Spawnable(const Spawnable& rhs) = delete;
Spawnable(Spawnable&& other);
~Spawnable() override = default;
Spawnable& operator=(const Spawnable& rhs) = delete;
Spawnable& operator=(Spawnable&& other);
const EntityList& GetEntities() const;
EntityList& GetEntities();
bool IsEmpty() const;
SpawnableMetaData& GetMetaData();
const SpawnableMetaData& GetMetaData() const;
static void Reflect(AZ::ReflectContext* context);
private:
SpawnableMetaData m_metaData;
// Container for keeping all entities of the prefab the Spawnable was created from.
// Includes both direct and nested entities of the prefab.
EntityList m_entities;
};
using SpawnableList = AZStd::vector<Spawnable>;
} // namespace AzFramework
@@ -0,0 +1,91 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Serialization/Utils.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/Spawnable/SpawnableAssetHandler.h>
#include <AzFramework/Spawnable/Spawnable.h>
namespace AzFramework
{
SpawnableAssetHandler::SpawnableAssetHandler()
{
AZ::AssetTypeInfoBus::MultiHandler::BusConnect(AZ::AzTypeInfo<Spawnable>::Uuid());
}
SpawnableAssetHandler::~SpawnableAssetHandler()
{
AZ::AssetTypeInfoBus::MultiHandler::BusDisconnect();
}
AZ::Data::AssetPtr SpawnableAssetHandler::CreateAsset(const AZ::Data::AssetId& id, [[maybe_unused]] const AZ::Data::AssetType& type)
{
AZ_Assert(type == AZ::AzTypeInfo<Spawnable>::Uuid(),
"Asset handler for Spawnable was given a type that's not a Spawnable: %s", type.ToString<AZStd::string>().c_str());
return aznew Spawnable(id);
}
void SpawnableAssetHandler::DestroyAsset(AZ::Data::AssetPtr ptr)
{
delete ptr;
}
void SpawnableAssetHandler::GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes)
{
assetTypes.push_back(AZ::AzTypeInfo<Spawnable>::Uuid());
}
auto SpawnableAssetHandler::LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB) -> LoadResult
{
Spawnable* spawnable = asset.GetAs<Spawnable>();
AZ_Assert(spawnable, "Loaded asset data handed to the SpawnableAssetHandler didn't contain a Spawanble.");
AZ::ObjectStream::FilterDescriptor filter(assetLoadFilterCB);
if (AZ::Utils::LoadObjectFromStreamInPlace(*stream, *spawnable, nullptr /*SerializeContext*/, filter))
{
return AZ::Data::AssetHandler::LoadResult::LoadComplete;
}
else
{
AZ_Error("Spawnable", false, "Failed to deserialize asset %s.", asset->GetId().ToString<AZStd::string>().c_str());
return AZ::Data::AssetHandler::LoadResult::Error;
}
}
AZ::Data::AssetType SpawnableAssetHandler::GetAssetType() const
{
return AZ::AzTypeInfo<Spawnable>::Uuid();
}
const char* SpawnableAssetHandler::GetAssetTypeDisplayName() const
{
return "Spawnable";
}
const char* SpawnableAssetHandler::GetGroup() const
{
return "Prefab";
}
const char* SpawnableAssetHandler::GetBrowserIcon() const
{
return "Editor/Icons/Components/Viewport/EntityInSlice.png";
}
void SpawnableAssetHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
{
extensions.push_back(Spawnable::FileExtension);
}
} // namespace AzFramework
@@ -0,0 +1,57 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetTypeInfoBus.h>
#include <AzCore/Memory/SystemAllocator.h>
namespace AzFramework
{
class SpawnableAssetHandler final
: public AZ::Data::AssetHandler
, public AZ::AssetTypeInfoBus::MultiHandler
{
public:
AZ_CLASS_ALLOCATOR(SpawnableAssetHandler, AZ::SystemAllocator, 0);
AZ_RTTI(AZ::SpawnableAssetHandler, "{BF6E2D17-87C9-4BB1-A205-3656CF6D551D}", AZ::Data::AssetHandler);
SpawnableAssetHandler();
~SpawnableAssetHandler() override;
//
// AssetHandler
//
AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override;
void DestroyAsset(AZ::Data::AssetPtr ptr) override;
void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override;
//
// AssetTypeInfoBus
//
AZ::Data::AssetType GetAssetType() const override;
const char* GetAssetTypeDisplayName() const override;
const char* GetGroup() const override;
const char* GetBrowserIcon() const override;
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override;
protected:
LoadResult LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB) override;
};
} // namespace AzFramework

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