Resolve MP Gem Ctrl+G changes with main

This commit is contained in:
puvvadar
2021-05-15 13:12:16 -07:00
1706 changed files with 23775 additions and 170986 deletions
-1
View File
@@ -1 +0,0 @@
*.xml
@@ -189,7 +189,7 @@ namespace AZ
if (!WasLoadSuccess(result.GetOutcome()))
{
// This if is a hack around fault in the JSON serialization system
// Jira: https://jira.agscollab.com/browse/LY-106587
// Jira: LY-106587
if (message != "No part of the string could be interpreted as a uuid.")
{
deserializeError.append(message);
-1
View File
@@ -1 +0,0 @@
*.xml
@@ -18,6 +18,7 @@
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/Preprocessor/Enum.h>
#include <AzCore/std/containers/bitset.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/string_view.h>
@@ -216,16 +217,14 @@ namespace AZ
/**
* Setting for each reference (Asset<T>) to control loading of referenced assets during serialization.
*/
enum class AssetLoadBehavior : u8
{
PreLoad = 0, ///< Serializer will "Pre load" dependencies, asset containers may load in parallel but will not signal AssetReady
QueueLoad = 1, ///< Serializer will queue an asynchronous load of the referenced asset and return the object to the user. User code should use the \ref AZ::Data::AssetBus to monitor for when it's ready.
NoLoad = 2, ///< Serializer will load reference information, but asset loading will be left to the user. User code should call Asset<T>::QueueLoad and use the \ref AZ::Data::AssetBus to monitor for when it's ready.
///< AssetContainers will skip NoLoad dependencies
AZ_ENUM_WITH_UNDERLYING_TYPE(AssetLoadBehavior, u8,
(PreLoad, 0), ///< Serializer will "Pre load" dependencies, asset containers may load in parallel but will not signal AssetReady
(QueueLoad, 1), ///< Serializer will queue an asynchronous load of the referenced asset and return the object to the user. User code should use the \ref AZ::Data::AssetBus to monitor for when it's ready.
(NoLoad, 2), ///< Serializer will load reference information, but asset loading will be left to the user. User code should call Asset<T>::QueueLoad and use the \ref AZ::Data::AssetBus to monitor for when it's ready.
///< AssetContainers will skip NoLoad dependencies
Count,
Default = QueueLoad,
};
(Default, QueueLoad)
);
struct AssetFilterInfo
{
@@ -1235,6 +1234,7 @@ namespace AZ
} // namespace ProductDependencyInfo
} // namespace Data
AZ_TYPE_INFO_SPECIALIZE(Data::AssetLoadBehavior, "{DAF9ECED-FEF3-4D7A-A220-8CFD6A5E6DA1}");
AZ_TYPE_INFO_TEMPLATE_WITH_NAME(AZ::Data::Asset, "Asset", "{C891BF19-B60C-45E2-BFD0-027D15DDC939}", AZ_TYPE_INFO_CLASS);
} // namespace AZ
@@ -269,13 +269,13 @@ namespace AZ
{
for (auto& [assetId, dependentAsset] : m_dependencies)
{
if (dependentAsset->IsReady())
if (dependentAsset->IsReady() || dependentAsset->IsError())
{
HandleReadyAsset(dependentAsset);
}
}
}
if (auto asset = m_rootAsset.GetStrongReference(); asset.IsReady())
if (auto asset = m_rootAsset.GetStrongReference(); asset.IsReady() || asset.IsError())
{
HandleReadyAsset(asset);
}
@@ -496,10 +496,10 @@ namespace AZ
m_waitingCount -= 1;
disconnectEbus = true;
if (m_waitingAssets.empty())
{
allReady = true;
}
}
if (m_waitingAssets.empty())
{
allReady = true;
}
}
@@ -510,8 +510,15 @@ namespace AZ
}
}
if (allReady && m_initComplete)
// If there are no assets left to be loaded, trigger the final AssetContainer notification (ready or canceled).
// We guard against prematurely sending it (m_initComplete) because it's possible for assets to get removed from our waiting
// list *while* we're still building up the list, so the list would appear to be empty too soon.
// We also guard against sending it multiple times (m_finalNotificationSent), because in some error conditions, it may be
// possible to try to remove the same asset multiple times, which if it's the last asset, it could trigger multiple
// notifications.
if (allReady && m_initComplete && !m_finalNotificationSent)
{
m_finalNotificationSent = true;
if (m_rootAsset)
{
AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetContainerReady, this);
@@ -137,6 +137,7 @@ namespace AZ
AZStd::atomic_int m_invalidDependencies{ 0 };
AZStd::unordered_set<AZ::Data::AssetId> m_unloadedDependencies;
AZStd::atomic_bool m_initComplete{ false };
AZStd::atomic_bool m_finalNotificationSent{false};
mutable AZStd::recursive_mutex m_preloadMutex;
// AssetId -> List of assets it is still waiting on
@@ -70,6 +70,17 @@ namespace AZ
}
}
{
const AZ::Data::AssetLoadBehavior autoLoadBehavior = instance->GetAutoLoadBehavior();
const AZ::Data::AssetLoadBehavior defaultAutoLoadBehavior = defaultInstance ?
defaultInstance->GetAutoLoadBehavior() : AZ::Data::AssetLoadBehavior::Default;
result.Combine(
ContinueStoringToJsonObjectField(outputValue, "loadBehavior",
&autoLoadBehavior, &defaultAutoLoadBehavior,
azrtti_typeid<Data::AssetLoadBehavior>(), context));
}
{
ScopedContextPath subPathHint(context, "m_assetHint");
const AZStd::string* hint = &instance->GetHint();
@@ -100,14 +111,28 @@ namespace AZ
AssetId id;
JSR::ResultCode result(JSR::Tasks::ReadField);
SerializedAssetTracker* assetTracker =
context.GetMetadata().Find<SerializedAssetTracker>();
{
Data::AssetLoadBehavior loadBehavior = instance->GetAutoLoadBehavior();
result =
ContinueLoadingFromJsonObjectField(&loadBehavior,
azrtti_typeid<Data::AssetLoadBehavior>(),
inputValue, "loadBehavior", context);
instance->SetAutoLoadBehavior(loadBehavior);
}
auto it = inputValue.FindMember("assetId");
if (it != inputValue.MemberEnd())
{
ScopedContextPath subPath(context, "assetId");
result = ContinueLoading(&id, azrtti_typeid<AssetId>(), it->value, context);
result.Combine(ContinueLoading(&id, azrtti_typeid<AssetId>(), it->value, context));
if (!id.m_guid.IsNull())
{
*instance = AssetManager::Instance().FindOrCreateAsset(id, instance->GetType(), AssetLoadBehavior::NoLoad);
*instance = AssetManager::Instance().FindOrCreateAsset(id, instance->GetType(), instance->GetAutoLoadBehavior());
result.Combine(context.Report(result, "Successfully created Asset<T> with id."));
@@ -142,6 +167,11 @@ namespace AZ
"The asset hint is missing for Asset<T>, so it will be left empty."));
}
if (assetTracker)
{
assetTracker->AddAsset(*instance);
}
bool success = result.GetOutcome() <= JSR::Outcomes::PartialSkip;
bool defaulted = result.GetOutcome() == JSR::Outcomes::DefaultsUsed || result.GetOutcome() == JSR::Outcomes::PartialDefaults;
AZStd::string_view message =
@@ -150,5 +180,20 @@ namespace AZ
"Not enough information was available to create an instance of Asset<T> or data was corrupted.";
return context.Report(result, message);
}
void SerializedAssetTracker::AddAsset(Asset<AssetData>& asset)
{
m_serializedAssets.emplace_back(asset);
}
const AZStd::vector<Asset<AssetData>>& SerializedAssetTracker::GetTrackedAssets() const
{
return m_serializedAssets;
}
AZStd::vector<Asset<AssetData>>& SerializedAssetTracker::GetTrackedAssets()
{
return m_serializedAssets;
}
} // namespace Data
} // namespace AZ
@@ -13,6 +13,7 @@
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
namespace AZ
@@ -37,5 +38,18 @@ namespace AZ
private:
JsonSerializationResult::Result LoadAsset(void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context);
};
class SerializedAssetTracker final
{
public:
AZ_RTTI(SerializedAssetTracker, "{1E067091-8C0A-44B1-A455-6E97663F6963}");
void AddAsset(Asset<AssetData>& asset);
AZStd::vector<Asset<AssetData>>& GetTrackedAssets();
const AZStd::vector<Asset<AssetData>>& GetTrackedAssets() const;
private:
AZStd::vector<Asset<AssetData>> m_serializedAssets;
};
} // namespace Data
} // namespace AZ
@@ -13,7 +13,7 @@
#include <AzCore/Asset/AssetJsonSerializer.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Preprocessor/EnumReflectUtils.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Asset/AssetManager.h>
@@ -24,6 +24,11 @@
namespace AZ
{
namespace Data
{
AZ_ENUM_DEFINE_REFLECT_UTILITIES(AssetLoadBehavior);
}
//=========================================================================
// AssetDatabaseComponent
// [6/25/2012]
@@ -99,6 +104,8 @@ namespace AZ
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
AZ::Data::AssetLoadBehaviorReflect(*serializeContext);
serializeContext->RegisterGenericType<Data::Asset<Data::AssetData>>();
serializeContext->Class<AssetManagerComponent, AZ::Component>()
@@ -916,27 +916,49 @@ namespace AZ
SetSettingsRegistrySpecializations(specializations);
AZStd::vector<char> scratchBuffer;
// Retrieves the list gem module build targets that the active project depends on
SettingsRegistryMergeUtils::MergeSettingsToRegistry_TargetBuildDependencyRegistry(registry,
AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
// In development builds apply the o3de registry and the command line to allow early overrides. This will
// allow developers to override things like default paths or Asset Processor connection settings. Any additional
// values will be replaced by later loads, so this step will happen again at the end of loading.
SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
// Project User Registry is merged after the command line here to allow make sure the any command line override of the project path
// is used for merging the project's user registry
SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
#endif
//! Retrieves the list gem targets that the project has load dependencies on
//! This populates the /Amazon/Gems/<GemName>/SourcePaths array entries which is required
//! by the MergeSettingsToRegistry_GemRegistry() function below to locate the gem's root folder
//! and merge in the gem's registry files.
//! But when running from a pre-built app from the O3DE SDK(Editor/AssetProcessor), the projects binary
//! directory is needed in order to located the load dependency registry files
//! That project binary folder is generated with the <ProjectRoot>/user/Registry when CMake is configured
//! for the project
//! Therefore the order of merging must be as follows
//! 1. MergeSettingsToRegistry_ProjectUserRegistry - Populates the /Amazon/Project/Settings/Build/project_build_path
//! which contains the path to the project binary directory
//! 2. MergeSettingsToRegistry_TargetBuildDependencyRegistry - Loads the cmake_dependencies.<project_name>.<application_name>.setreg
//! file from the locations in order of
//! 1. <executable_directory>/Registry
//! 2. <cache_root>/Registry
//! 3. <project_build_path>/bin/$<CONFIG>/Registry
//! 3. MergeSettingsToRegistry_GemRegistries - Merges the settings registry files from each gem's <GemRoot>/Registry directory
SettingsRegistryMergeUtils::MergeSettingsToRegistry_TargetBuildDependencyRegistry(registry,
AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_EngineRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_GemRegistries(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, true);
#endif
// Update the Runtime file paths in case the "{BootstrapSettingsRootKey}/assets" key was overriden by a setting registry
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
}
void ComponentApplication::SetSettingsRegistrySpecializations(SettingsRegistryInterface::Specializations& specializations)
@@ -1304,7 +1326,7 @@ namespace AZ
// Add all auto loadable non-asset gems to the list of gem modules to load
if (!moduleLoadData.m_autoLoad)
{
break;
continue;
}
for (AZ::OSString& dynamicLibraryPath : moduleLoadData.m_dynamicLibraryPaths)
{
@@ -24,9 +24,7 @@ namespace AZ
AZ_TYPE_INFO_SPECIALIZE(AZStd::chrono::system_clock::time_point, "{5C48FD59-7267-405D-9C06-1EA31379FE82}");
/**
* Wrapper that reflects a AZStd::chrono::system_clock::time_point to script.
*/
//! Wrapper that reflects a AZStd::chrono::system_clock::time_point to script.
class ScriptTimePoint
{
public:
@@ -38,33 +36,45 @@ namespace AZ
explicit ScriptTimePoint(AZStd::chrono::system_clock::time_point timePoint)
: m_timePoint(timePoint) {}
AZStd::string ToString() const
{
return AZStd::string::format("Time %llu", m_timePoint.time_since_epoch().count());
}
//! Formats the time point in a string formatted as: "Time <seconds since epoch>".
AZStd::string ToString() const;
const AZStd::chrono::system_clock::time_point& Get() { return m_timePoint; }
//! Returns the time point.
const AZStd::chrono::system_clock::time_point& Get() const;
// Returns the time point in seconds
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 seconds
double GetSeconds() const;
// Returns the time point in milliseconds
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();
}
//! Returns the time point in milliseconds
double GetMilliseconds() const;
static void Reflect(ReflectContext* reflection);
protected:
AZStd::chrono::system_clock::time_point m_timePoint;
};
inline AZStd::string ScriptTimePoint::ToString() const
{
return AZStd::string::format("Time %llu", m_timePoint.time_since_epoch().count());
}
inline const AZStd::chrono::system_clock::time_point& ScriptTimePoint::Get() const
{
return m_timePoint;
}
inline double ScriptTimePoint::GetSeconds() const
{
typedef AZStd::chrono::duration<double> double_seconds;
return AZStd::chrono::duration_cast<double_seconds>(m_timePoint.time_since_epoch()).count();
}
inline double ScriptTimePoint::GetMilliseconds() const
{
typedef AZStd::chrono::duration<double, AZStd::milli> double_ms;
return AZStd::chrono::duration_cast<double_ms>(m_timePoint.time_since_epoch()).count();
}
}
@@ -14,8 +14,6 @@
#include <limits>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Memory/SystemAllocator.h>
@@ -599,6 +599,34 @@ namespace AZ::SettingsRegistryMergeUtils
? devWriteStorage.value()
: projectUserPath.Native());
// Set the project in-memory build path if the ProjectBuildPath key has been supplied
if (AZ::IO::FixedMaxPath projectBuildPath; registry.Get(projectBuildPath.Native(), ProjectBuildPath))
{
registry.Remove(FilePathKey_ProjectBuildPath);
registry.Remove(FilePathKey_ProjectConfigurationBinPath);
AZ::IO::FixedMaxPath buildConfigurationPath = normalizedProjectPath / projectBuildPath;
if (IO::SystemFile::Exists(buildConfigurationPath.c_str()))
{
registry.Set(FilePathKey_ProjectBuildPath, buildConfigurationPath.LexicallyNormal().Native());
}
// Add the specific build configuration paths to the Settings Registry
// First try <project-build-path>/bin/$<CONFIG> and if that path doesn't exist
// try <project-build-path>/bin/$<PLATFORM>/$<CONFIG>
buildConfigurationPath /= "bin";
if (IO::SystemFile::Exists((buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
{
registry.Set(FilePathKey_ProjectConfigurationBinPath,
(buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).LexicallyNormal().Native());
}
else if (IO::SystemFile::Exists((buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
{
registry.Set(FilePathKey_ProjectConfigurationBinPath,
(buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).LexicallyNormal().Native());
}
}
// Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name.
auto projectNameKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey)
@@ -689,6 +717,14 @@ namespace AZ::SettingsRegistryMergeUtils
mergePath /= SettingsRegistryInterface::RegistryFolder;
registry.MergeSettingsFolder(mergePath.Native(), specializations, platform, "", scratchBuffer);
}
AZ::IO::FixedMaxPath projectBinPath;
if (registry.Get(projectBinPath.Native(), FilePathKey_ProjectConfigurationBinPath))
{
// Append the project build path path to the project root
projectBinPath /= SettingsRegistryInterface::RegistryFolder;
registry.MergeSettingsFolder(projectBinPath.Native(), specializations, platform, "", scratchBuffer);
}
}
void MergeSettingsToRegistry_EngineRegistry(SettingsRegistryInterface& registry, const AZStd::string_view platform,
@@ -934,7 +970,8 @@ namespace AZ::SettingsRegistryMergeUtils
"project-path", AZStd::string::format("%s/project_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)},
OptionKeyToRegsetKey{
"project-cache-path",
AZStd::string::format("%s/project_cache_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)}};
AZStd::string::format("%s/project_cache_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)},
OptionKeyToRegsetKey{"project-build-path", ProjectBuildPath} };
AZStd::fixed_vector<AZStd::string, commandOptions.size()> overrideArgs;
@@ -52,6 +52,14 @@ namespace AZ::SettingsRegistryMergeUtils
//! project settings can be stored
inline static constexpr char FilePathKey_ProjectUserPath[] = "/Amazon/AzCore/Runtime/FilePaths/SourceProjectUserPath";
//! User facing key which represents the root of a project cmake build tree. i.e the ${CMAKE_BINARY_DIR}
//! A relative path is taking relative to the *project* root, NOT *engine* root.
inline constexpr AZStd::string_view ProjectBuildPath = "/Amazon/Project/Settings/Build/project_build_path";
//! In-Memory only key which stores an absolute path to the project build directory
inline constexpr AZStd::string_view FilePathKey_ProjectBuildPath = "/Amazon/AzCore/Runtime/FilePaths/ProjectBuildPath";
//! In-Memory only key which stores the configuration directory containing the built binaries
inline constexpr AZStd::string_view FilePathKey_ProjectConfigurationBinPath = "/Amazon/AzCore/Runtime/FilePaths/ProjectConfigurationBinPath";
//! 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";
@@ -128,7 +136,7 @@ namespace AZ::SettingsRegistryMergeUtils
//! Callback function that is after a has been filtered through the CommentPrefixFunc
//! to determine if the text matches a section header
//! returns a view of the section name if the line contains a section
//! Otherwise an empty view is returend
//! Otherwise an empty view is returned
using SectionHeaderFunc = AZStd::function<AZStd::string_view(AZStd::string_view line)>;
//! Root JSON pointer path to place all key=values pairs of configuration data within
+22 -11
View File
@@ -1,14 +1,14 @@
/*
* 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.
*
*/
* 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
@@ -17,4 +17,15 @@
namespace AZStd
{
using std::abs;
}
using std::acos;
using std::asin;
using std::atan;
using std::atan2;
using std::cos;
using std::exp2;
using std::fmod;
using std::round;
using std::sin;
using std::sqrt;
using std::tan;
} // namespace AZStd
@@ -17,8 +17,9 @@ namespace AZ
{
namespace Platform
{
void GetModulePath(AZ::OSString& path)
AZ::IO::FixedMaxPath GetModulePath()
{
return {};
}
void* OpenModule(const AZ::OSString& fileName, bool&)
@@ -26,10 +27,9 @@ namespace AZ
// Android 19 does not have RTLD_NOLOAD but it should be OK since only the Editor expects to reopen modules
return dlopen(fileName.c_str(), RTLD_NOW);
}
void ConstructModuleFullFileName(const AZ::OSString& path, const AZ::OSString& fileName, AZ::OSString& fullPath)
void ConstructModuleFullFileName(AZ::IO::FixedMaxPath&)
{
fullPath = path + fileName;
}
}
}
@@ -10,7 +10,6 @@
*
*/
#include <AzCore/IO/SystemFile.h> // for AZ_MAX_PATH_LEN
#include <AzCore/std/string/osstring.h>
#include <AzCore/Utils/Utils.h>
#include <dlfcn.h>
@@ -19,15 +18,9 @@ namespace AZ
{
namespace Platform
{
void GetModulePath(AZ::OSString& path)
AZ::IO::FixedMaxPath GetModulePath()
{
char exePath[AZ_MAX_PATH_LEN];
if (AZ::Utils::GetExecutableDirectory(exePath, AZ_ARRAY_SIZE(exePath)) ==
AZ::Utils::ExecutablePathResult::Success)
{
path = exePath;
path.push_back('/');
}
return AZ::Utils::GetExecutableDirectory();
}
void* OpenModule(const AZ::OSString& fileName, bool& alreadyOpen)
@@ -40,10 +33,9 @@ namespace AZ
}
return handle;
}
void ConstructModuleFullFileName(const AZ::OSString& path, const AZ::OSString& fileName, AZ::OSString& fullPath)
void ConstructModuleFullFileName(AZ::IO::FixedMaxPath&)
{
fullPath = path + fileName;
}
}
}
@@ -11,9 +11,11 @@
*/
#include <AzCore/Module/DynamicModuleHandle.h>
#include <AzCore/IO/SystemFile.h> // for AZ_MAX_PATH_LEN
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <dlfcn.h>
#include <libgen.h>
@@ -21,9 +23,9 @@ namespace AZ
{
namespace Platform
{
void GetModulePath(AZ::OSString& path);
AZ::IO::FixedMaxPath GetModulePath();
void* OpenModule(const AZ::OSString& fileName, bool& alreadyOpen);
void ConstructModuleFullFileName(const AZ::OSString& path, const AZ::OSString& fileName, AZ::OSString& fullPath);
void ConstructModuleFullFileName(AZ::IO::FixedMaxPath& fullPath);
}
class DynamicModuleHandleUnixLike
@@ -36,40 +38,55 @@ namespace AZ
: DynamicModuleHandle(fullFileName)
, m_handle(nullptr)
{
AZ::OSString path;
AZ::OSString fileName;
AZ::OSString fullPath = "";
AZ::OSString::size_type finalSlash = m_fileName.find_last_of("/");
if (finalSlash != AZ::OSString::npos)
AZ::IO::FixedMaxPath fullFilePath(AZStd::string_view{m_fileName});
if (fullFilePath.HasFilename())
{
// Path up to and including final slash
path = m_fileName.substr(0, finalSlash + 1);
// Everything after the final slash
// If m_fileName ends in /, the end result is path/lib.dylib, which just fails to load.
fileName = m_fileName.substr(finalSlash + 1);
}
else
{
// If no slash found, assume empty path, only file name
path = "";
Platform::GetModulePath(path);
fileName = m_fileName;
AZ::IO::FixedMaxPathString fileNamePath{fullFilePath.Filename().Native()};
if (!fileNamePath.starts_with(AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX))
{
fileNamePath = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX + fileNamePath;
}
if (!fileNamePath.ends_with(AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION))
{
fileNamePath += AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
}
fullFilePath.ReplaceFilename(AZStd::string_view(fileNamePath));
}
if (fileName.substr(0, 3) != AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX)
Platform::ConstructModuleFullFileName(fullFilePath);
// Check if the module exist at the given path within the current working directory
// If it doesn't attempt to append the path to the executable path
if (!AZ::IO::SystemFile::Exists(fullFilePath.c_str()))
{
fileName = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX + fileName;
auto candidatePath = Platform::GetModulePath() / fullFilePath;
if (AZ::IO::SystemFile::Exists(candidatePath.c_str()))
{
fullFilePath = candidatePath;
}
}
size_t extensionLen = strlen(AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION);
if (fileName.substr(fileName.length() - extensionLen, extensionLen) != AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION)
// If the path still doesn't exist at this point, check the SettingsRegistryMergeUtils
// FilePathKey_ProjectBuildPath key to see if a project-build-path argument has been supplied
if (!AZ::IO::SystemFile::Exists(fullFilePath.c_str()))
{
fileName = fileName + AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if(AZ::IO::FixedMaxPath projectModulePath;
settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
{
projectModulePath /= fullFilePath;
if (AZ::IO::SystemFile::Exists(projectModulePath.c_str()))
{
fullFilePath = projectModulePath;
}
}
}
}
Platform::ConstructModuleFullFileName(path, fileName, fullPath);
m_fileName = fullPath;
m_fileName = AZStd::string_view{fullFilePath.Native()};
}
~DynamicModuleHandleUnixLike() override
@@ -81,9 +98,9 @@ namespace AZ
{
AZ::Debug::Trace::Printf("Module", "Attempting to load module:%s\n", m_fileName.c_str());
bool alreadyOpen = false;
m_handle = Platform::OpenModule(m_fileName, alreadyOpen);
if(m_handle)
{
if (alreadyOpen)
@@ -15,6 +15,7 @@
#include <AzCore/Module/DynamicModuleHandle.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/PlatformIncl.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
namespace AZ
@@ -31,13 +32,13 @@ namespace AZ
{
// Ensure filename ends in ".dll"
// Otherwise filenames like "gem.1.0.0" fail to load (.0 is assumed to be the extension).
if (m_fileName.substr(m_fileName.length() - 4) != AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION)
if (!m_fileName.ends_with(AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION))
{
m_fileName = m_fileName + AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
m_fileName += AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
}
AZ::IO::PathView modulePathView{ m_fileName };
// If the module path doesn't have a directory within it, prepend it to the path
// If the module path doesn't have a directory within it, prepend the executable directory to the path
// and check if the new path exist
if (modulePathView.HasFilename() && !modulePathView.HasParentPath())
{
@@ -54,6 +55,27 @@ namespace AZ
}
}
}
// If the module file path does not exist, attempt to search for the module within
// the project's build directory
if (!AZ::IO::SystemFile::Exists(m_fileName.c_str()))
{
// The Settings Registry may not exist in early startup if modules are loaded
// before the ComponentApplication is crated(such as in the Editor main.cpp)
// Therefore an existence check is needed
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if(AZ::IO::FixedMaxPath projectModulePath;
settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
{
projectModulePath /= AZStd::string_view(m_fileName);
if (AZ::IO::SystemFile::Exists(projectModulePath.c_str()))
{
m_fileName.assign(projectModulePath.c_str(), projectModulePath.Native().size());
}
}
}
}
}
~DynamicModuleHandleWindows() override
@@ -10,7 +10,6 @@
*
*/
#include <AzCore/IO/SystemFile.h> // for AZ_MAX_PATH_LEN
#include <AzCore/std/string/osstring.h>
#include <AzCore/Utils/Utils.h>
#include <dlfcn.h>
@@ -19,15 +18,9 @@ namespace AZ
{
namespace Platform
{
void GetModulePath(AZ::OSString& path)
AZ::IO::FixedMaxPath GetModulePath()
{
char exePath[AZ_MAX_PATH_LEN];
if (AZ::Utils::GetExecutableDirectory(exePath, AZ_ARRAY_SIZE(exePath)) ==
AZ::Utils::ExecutablePathResult::Success)
{
path = exePath;
path.push_back('/');
}
return AZ::Utils::GetExecutableDirectory();
}
void* OpenModule(const AZ::OSString& fileName, bool& alreadyOpen)
@@ -40,10 +33,9 @@ namespace AZ
}
return handle;
}
void ConstructModuleFullFileName(const AZ::OSString& path, const AZ::OSString& fileName, AZ::OSString& fullPath)
void ConstructModuleFullFileName(AZ::IO::FixedMaxPath&)
{
fullPath = path + fileName;
}
}
}
@@ -10,7 +10,6 @@
*
*/
#include <AzCore/IO/SystemFile.h> // for AZ_MAX_PATH_LEN
#include <AzCore/std/string/osstring.h>
#include <AzCore/Utils/Utils.h>
#include <dlfcn.h>
@@ -19,15 +18,9 @@ namespace AZ
{
namespace Platform
{
void GetModulePath(AZ::OSString& path)
AZ::IO::FixedMaxPath GetModulePath()
{
char exePath[AZ_MAX_PATH_LEN];
if (AZ::Utils::GetExecutableDirectory(exePath, AZ_ARRAY_SIZE(exePath)) ==
AZ::Utils::ExecutablePathResult::Success)
{
AZ::OSString frameworks = "/Frameworks/";
path = exePath + frameworks;
}
return AZ::IO::FixedMaxPath(AZ::Utils::GetExecutableDirectory()) / "Frameworks";
}
void* OpenModule(const AZ::OSString& fileName, bool& alreadyOpen)
@@ -40,10 +33,15 @@ namespace AZ
}
return handle;
}
void ConstructModuleFullFileName(const AZ::OSString& path, const AZ::OSString& fileName, AZ::OSString& fullPath)
void ConstructModuleFullFileName(AZ::IO::FixedMaxPath& fullPath)
{
fullPath = path + fileName + ".framework/" + fileName;
// Append .framework to the name of full path
// Afterwards use the AZ::IO::Path Append function append the filename as a child
// of the framework directory
AZ::IO::FixedMaxPathString fileName = fullPath.Filename().Native();
fullPath.ReplaceFilename(fileName + ".framework");
fullPath /= fileName;
}
}
}
@@ -1042,11 +1042,7 @@ namespace UnitTest
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
TEST_F(AssetJobsFloodTest, DISABLED_ContainerCoreTest_BasicDependencyManagement_Success)
#else
TEST_F(AssetJobsFloodTest, ContainerCoreTest_BasicDependencyManagement_Success)
#endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
{
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect();
// Setup has already created/destroyed assets
@@ -351,6 +351,7 @@ namespace UnitTest
public AZ::Data::AssetCatalog
{
static inline const AZ::Uuid TestAssetId{"{E970B177-5F45-44EB-A2C4-9F29D9A0B2A2}"};
static inline const AZ::Uuid MissingAssetId{"{11111111-1111-1111-1111-111111111111}"};
static inline constexpr AZStd::string_view TestAssetPath = "test";
void SetUp() override
@@ -431,24 +432,40 @@ namespace UnitTest
// AssetCatalogRequestBus implementation
// Minimalist mocks to provide our desired asset path or asset id
AZStd::string GetAssetPathById([[maybe_unused]] const AZ::Data::AssetId& id) override
AZStd::string GetAssetPathById(const AZ::Data::AssetId& id) override
{
return TestAssetPath;
if (id == TestAssetId)
{
return TestAssetPath;
}
return "";
}
AZ::Data::AssetId GetAssetIdByPath(
[[maybe_unused]] const char* path, [[maybe_unused]] const AZ::Data::AssetType& typeToRegister,
const char* path, [[maybe_unused]] const AZ::Data::AssetType& typeToRegister,
[[maybe_unused]] bool autoRegisterIfNotFound) override
{
return TestAssetId;
if (path == TestAssetPath)
{
return TestAssetId;
}
return AZ::Data::AssetId();
}
// Return the mocked-out information for our test asset
AZ::Data::AssetInfo GetAssetInfoById([[maybe_unused]] const AZ::Data::AssetId& id) override
AZ::Data::AssetInfo GetAssetInfoById(const AZ::Data::AssetId& id) override
{
AZ::Data::AssetInfo assetInfo;
assetInfo.m_assetId = TestAssetId;
assetInfo.m_assetType = AZ::AzTypeInfo<EmptyAsset>::Uuid();
assetInfo.m_relativePath = TestAssetPath;
if (id == TestAssetId)
{
assetInfo.m_assetId = TestAssetId;
assetInfo.m_assetType = AZ::AzTypeInfo<EmptyAsset>::Uuid();
assetInfo.m_relativePath = TestAssetPath;
}
return assetInfo;
}
@@ -456,15 +473,20 @@ namespace UnitTest
// Set the mocked-out asset load to have a 0-byte length so that the load skips I/O and immediately returns success
AZ::Data::AssetStreamInfo GetStreamInfoForLoad(
[[maybe_unused]] const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override
const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override
{
EXPECT_TRUE(type == AZ::AzTypeInfo<EmptyAsset>::Uuid());
AZ::Data::AssetStreamInfo info;
info.m_dataOffset = 0;
info.m_streamName = TestAssetPath;
info.m_dataLen = 0;
info.m_streamFlags = AZ::IO::OpenMode::ModeRead;
if (id == TestAssetId)
{
info.m_streamName = TestAssetPath;
}
return info;
}
@@ -489,4 +511,27 @@ namespace UnitTest
EXPECT_TRUE(testAsset.IsReady());
}
// This test verifies that even if the asset loading returns immediately with an error, all of the loading code works
// successfully. The test itself loads a missing asset twice - the first time is a non-immediate error, where the error
// isn't reported until the DispatchEvents() call. The second time is an immediate error, because now the asset is already
// registered in an Error state. If the test fails, it will likely get caught in the shutdown of the test class, if any
// assets still exist at the point that the asset handler is unregistered. If they're present, then handling of the immediate
// error didn't work, as it left around extra references to the asset that haven't been cleaned up.
TEST_F(AssetManagerStreamerImmediateCompletionTests, ImmediateAssetError_WorksSuccessfully)
{
AZ::Data::AssetLoadParameters loadParams;
// Attempt to load a missing asset the first time. It will get an error, but not until the DispatchEvents() call happens.
auto testAsset1 = AssetManager::Instance().GetAsset<EmptyAsset>(MissingAssetId, AZ::Data::AssetLoadBehavior::Default, loadParams);
AZ::Data::AssetManager::Instance().DispatchEvents();
EXPECT_TRUE(testAsset1.IsError());
// While the reference to the missing asset still exists, try to get it again. This will cause a more immediate error in
// the AssetContainer code, which should still get handled correctly. In the failure condition, it will instead leave the
// AssetContainer in a state where it never sends the final OnAssetContainerReady/Canceled message.
auto testAsset2 = AssetManager::Instance().GetAsset<EmptyAsset>(MissingAssetId, AZ::Data::AssetLoadBehavior::Default, loadParams);
AZ::Data::AssetManager::Instance().DispatchEvents();
EXPECT_TRUE(testAsset2.IsError());
}
} // namespace UnitTest
@@ -135,6 +135,7 @@ namespace JsonSerializationTests
auto instance = AZStd::make_shared<Asset>();
instance->Create(id, false);
instance->SetHint("TestFile");
instance->SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad);
return instance;
}
@@ -158,6 +159,7 @@ namespace JsonSerializationTests
"guid": "{BBEAC89F-8BAD-4A9D-BF6E-D0DF84A8DFD6}",
"subId": 1
},
"loadBehavior": "PreLoad",
"assetHint": "TestFile"
})";
}
@@ -52,8 +52,6 @@
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/IO/RemoteStorageDrive.h>
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Network/NetBindingSystemComponent.h>
#include <AzFramework/Physics/Utils.h>
#include <AzFramework/Render/GameIntersectorComponent.h>
#include <AzFramework/Platform/PlatformDefaults.h>
@@ -66,7 +64,6 @@
#include <AzFramework/TargetManagement/TargetManagementComponent.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Driller/RemoteDrillerInterface.h>
#include <AzFramework/Network/NetworkContext.h>
#include <AzFramework/Metrics/MetricsPlainTextNameRegistration.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
@@ -197,7 +194,6 @@ namespace AzFramework
ApplicationRequests::Bus::Handler::BusConnect();
AZ::UserSettingsFileLocatorBus::Handler::BusConnect();
NetSystemRequestBus::Handler::BusConnect();
}
Application::~Application()
@@ -207,7 +203,6 @@ namespace AzFramework
Stop();
}
NetSystemRequestBus::Handler::BusDisconnect();
AZ::UserSettingsFileLocatorBus::Handler::BusDisconnect();
ApplicationRequests::Bus::Handler::BusDisconnect();
@@ -285,13 +280,6 @@ namespace AzFramework
m_pimpl.reset();
/* The following line of code is a temporary fix.
* GridMate's ReplicaChunkDescriptor is stored in a global environment variable 'm_globalDescriptorTable'
* which does not get cleared when Application shuts down. We need to un-reflect here to clear ReplicaChunkDescriptor
* so that ReplicaChunkDescriptor::m_vdt doesn't get flooded when we repeatedly instantiate Application in unit tests.
*/
AZ::ReflectionEnvironment::GetReflectionManager()->RemoveReflectContext<NetworkContext>();
// Free any memory owned by the command line container.
m_commandLine = CommandLine();
@@ -320,8 +308,6 @@ namespace AzFramework
azrtti_typeid<AzFramework::AssetCatalogComponent>(),
azrtti_typeid<AzFramework::CustomAssetTypeComponent>(),
azrtti_typeid<AzFramework::FileTag::ExcludeFileComponent>(),
azrtti_typeid<AzFramework::NetBindingComponent>(),
azrtti_typeid<AzFramework::NetBindingSystemComponent>(),
azrtti_typeid<AzFramework::TransformComponent>(),
azrtti_typeid<AzFramework::SceneSystemComponent>(),
azrtti_typeid<AzFramework::AzFrameworkConfigurationSystemComponent>(),
@@ -457,9 +443,6 @@ namespace AzFramework
void Application::CreateReflectionManager()
{
ComponentApplication::CreateReflectionManager();
// Setup NetworkContext
AZ::ReflectionEnvironment::GetReflectionManager()->AddReflectContext<NetworkContext>();
}
////////////////////////////////////////////////////////////////////////////
@@ -479,19 +462,6 @@ namespace AzFramework
return uuid;
}
////////////////////////////////////////////////////////////////////////////
NetworkContext* Application::GetNetworkContext()
{
NetworkContext* result = nullptr;
if (auto reflectionManager = AZ::ReflectionEnvironment::GetReflectionManager())
{
result = reflectionManager->GetReflectContext<NetworkContext>();
}
return result;
}
void Application::ResolveEnginePath(AZStd::string& engineRelativePath) const
{
AZ::IO::FixedMaxPath fullPath = m_engineRoot / engineRelativePath;
@@ -21,7 +21,6 @@
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzFramework/Network/NetSystemBus.h>
#include <AzFramework/CommandLine/CommandLine.h>
#include <AzFramework/API/ApplicationAPI.h>
@@ -49,7 +48,6 @@ namespace AzFramework
: public AZ::ComponentApplication
, public AZ::UserSettingsFileLocatorBus::Handler
, public ApplicationRequests::Bus::Handler
, public NetSystemRequestBus::Handler
{
public:
// Base class for platform specific implementations of the application.
@@ -138,11 +136,6 @@ namespace AzFramework
// Convenience function that should be called instead of the standard exit() function to ensure platform requirements are met.
static void Exit(int errorCode) { ApplicationRequests::Bus::Broadcast(&ApplicationRequests::TerminateOnError, errorCode); }
//////////////////////////////////////////////////////////////////////////
//! NetSystemEventBus::Handler
//////////////////////////////////////////////////////////////////////////
NetworkContext* GetNetworkContext() override;
protected:
/**
@@ -24,7 +24,7 @@ namespace AZ::IO
ePakPriorityPakOnly = 2
};
// variables that control behavior of Archive/StreamEngine subsystems
// variables that control behavior of the Archive subsystem
struct ArchiveVars
{
#if defined(_RELEASE)
@@ -22,8 +22,6 @@
#include <AzFramework/Entity/GameEntityContextComponent.h>
#include <AzFramework/FileTag/FileTagComponent.h>
#include <AzFramework/Input/System/InputSystemComponent.h>
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Network/NetBindingSystemComponent.h>
#include <AzFramework/Render/GameIntersectorComponent.h>
#include <AzFramework/Scene/SceneSystemComponent.h>
#include <AzFramework/Script/ScriptComponent.h>
@@ -42,8 +40,6 @@ namespace AzFramework
AzFramework::AssetCatalogComponent::CreateDescriptor(),
AzFramework::CustomAssetTypeComponent::CreateDescriptor(),
AzFramework::FileTag::ExcludeFileComponent::CreateDescriptor(),
AzFramework::NetBindingComponent::CreateDescriptor(),
AzFramework::NetBindingSystemComponent::CreateDescriptor(),
AzFramework::TransformComponent::CreateDescriptor(),
AzFramework::NonUniformScaleComponent::CreateDescriptor(),
AzFramework::GameEntityContextComponent::CreateDescriptor(),
@@ -37,29 +37,6 @@ namespace AzFramework
void NonUniformScaleComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("NonUniformScaleService"));
incompatible.push_back(AZ_CRC_CE("DebugDrawObbService"));
incompatible.push_back(AZ_CRC_CE("DebugDrawService"));
incompatible.push_back(AZ_CRC_CE("EMotionFXActorService"));
incompatible.push_back(AZ_CRC_CE("EMotionFXSimpleMotionService"));
incompatible.push_back(AZ_CRC_CE("GradientTransformService"));
incompatible.push_back(AZ_CRC_CE("LegacyMeshService"));
incompatible.push_back(AZ_CRC_CE("LookAtService"));
incompatible.push_back(AZ_CRC_CE("SequenceService"));
incompatible.push_back(AZ_CRC_CE("ClothMeshService"));
incompatible.push_back(AZ_CRC_CE("PhysXJointService"));
incompatible.push_back(AZ_CRC_CE("PhysXCharacterControllerService"));
incompatible.push_back(AZ_CRC_CE("PhysXRagdollService"));
incompatible.push_back(AZ_CRC_CE("WhiteBoxService"));
incompatible.push_back(AZ_CRC_CE("NavigationAreaService"));
incompatible.push_back(AZ_CRC_CE("GeometryService"));
incompatible.push_back(AZ_CRC_CE("CapsuleShapeService"));
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("SphereShapeService"));
incompatible.push_back(AZ_CRC_CE("SplineService"));
incompatible.push_back(AZ_CRC_CE("TubeShapeService"));
}
void NonUniformScaleComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
@@ -878,15 +878,15 @@ namespace AzFramework
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TransformComponent, AZ::Component, NetBindable>()
->Version(4, &TransformComponentVersionConverter)
serializeContext->ClassDeprecate("NetBindable", "{80206665-D429-4703-B42E-94434F82F381}");
serializeContext->Class<TransformComponent, AZ::Component>()
->Version(5, &TransformComponentVersionConverter)
->Field("Parent", &TransformComponent::m_parentId)
->Field("Transform", &TransformComponent::m_worldTM)
->Field("LocalTransform", &TransformComponent::m_localTM)
->Field("ParentActivationTransformMode", &TransformComponent::m_parentActivationTransformMode)
->Field("IsStatic", &TransformComponent::m_isStatic)
->Field("InterpolatePosition", &TransformComponent::m_interpolatePosition)
->Field("InterpolateRotation", &TransformComponent::m_interpolateRotation)
;
}
@@ -17,7 +17,6 @@
#include <AzCore/Component/EntityBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/EBus/Event.h>
#include <AzFramework/Network/NetBindable.h>
namespace AzToolsFramework
{
@@ -41,10 +40,9 @@ namespace AzFramework
, public AZ::TransformBus::Handler
, public AZ::TransformNotificationBus::Handler
, private AZ::TransformHierarchyInformationBus::Handler
, public NetBindable
{
public:
AZ_COMPONENT(TransformComponent, AZ::TransformComponentTypeId, NetBindable, AZ::TransformInterface);
AZ_COMPONENT(TransformComponent, AZ::TransformComponentTypeId, AZ::TransformInterface);
friend class AzToolsFramework::Components::TransformComponent;
@@ -218,11 +216,5 @@ namespace AzFramework
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.
//! @deprecated
//! @{
AZ::InterpolationMode m_interpolatePosition = AZ::InterpolationMode::NoInterpolation;
AZ::InterpolationMode m_interpolateRotation = AZ::InterpolationMode::NoInterpolation;
//! @}
};
} // namespace AZ
@@ -40,17 +40,18 @@ namespace AzFramework
//! Standard parameters for drawing text on screen
struct TextDrawParameters
{
ViewportId m_drawViewportId = InvalidViewportId; //! Viewport to draw into
AZ::Vector3 m_position; //! world space position for 3d draws, screen space x,y,depth for 2d.
AZ::Color m_color = AZ::Colors::White; //! Color to draw the text
AZ::Vector2 m_scale = AZ::Vector2(1.0f); //! font scale
TextHorizontalAlignment m_hAlign = TextHorizontalAlignment::Left; //! Horizontal text alignment
TextVerticalAlignment m_vAlign = TextVerticalAlignment::Top; //! Vertical text alignment
bool m_monospace = false; //! disable character proportional spacing
bool m_depthTest = false; //! Test character against the depth buffer
bool m_virtual800x600ScreenSize = true; //! Text placement and size are scaled relative to a virtual 800x600 resolution
bool m_scaleWithWindow = false; //! Font gets bigger as the window gets bigger
bool m_multiline = true; //! text respects ascii newline characters
ViewportId m_drawViewportId = InvalidViewportId; //!< Viewport to draw into
AZ::Vector3 m_position; //!< world space position for 3d draws, screen space x,y,depth for 2d.
AZ::Color m_color = AZ::Colors::White; //!< Color to draw the text
AZ::Vector2 m_scale = AZ::Vector2(1.0f); //!< font scale
float m_lineSpacing; //!< Spacing between new lines, as a percentage of m_scale.
TextHorizontalAlignment m_hAlign = TextHorizontalAlignment::Left; //!< Horizontal text alignment
TextVerticalAlignment m_vAlign = TextVerticalAlignment::Top; //!< Vertical text alignment
bool m_monospace = false; //!< disable character proportional spacing
bool m_depthTest = false; //!< Test character against the depth buffer
bool m_virtual800x600ScreenSize = true; //!< Text placement and size are scaled relative to a virtual 800x600 resolution
bool m_scaleWithWindow = false; //!< Font gets bigger as the window gets bigger
bool m_multiline = true; //!< text respects ascii newline characters
};
class FontDrawInterface
@@ -63,10 +64,13 @@ namespace AzFramework
virtual void DrawScreenAlignedText2d(
const TextDrawParameters& params,
const AZStd::string_view& string) = 0;
AZStd::string_view text) = 0;
virtual void DrawScreenAlignedText3d(
const TextDrawParameters& params,
const AZStd::string_view& string) = 0;
AZStd::string_view text) = 0;
virtual AZ::Vector2 GetTextSize(
const TextDrawParameters& params,
AZStd::string_view text) = 0;
};
class FontQueryInterface
@@ -1,146 +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
#ifndef AZFRAMEWORK_NETWORK_DYNAMICSERIALIZABLEFIELDMARSHALER_H
#define AZFRAMEWORK_NETWORK_DYNAMICSERIALIZABLEFIELDMARSHALER_H
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/Serialization/DynamicSerializableField.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Serialize/MathMarshal.h>
#include <GridMate/Serialize/UuidMarshal.h>
namespace GridMate
{
/**
* Marshaler for DynamicSerializableField, contains a template param for allocating the memory buffer that it's going to use to write to.
*/
template<size_t BufferSize>
class DynamicSerializableFieldMarshaler
{
public:
DynamicSerializableFieldMarshaler()
: m_serializeContext(nullptr)
{
EBUS_EVENT_RESULT(m_serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
}
// Mainly here for unit test purposes.
DynamicSerializableFieldMarshaler(AZ::SerializeContext* context)
: m_serializeContext(context)
{
}
AZ_FORCE_INLINE void Marshal(WriteBuffer& wb, const AZ::DynamicSerializableField& value) const
{
AZ_Error("DynamicSerializableFieldMarshaler", m_serializeContext, "Unknown SerializationContext. Aborting Marshal attempt.\n");
if (m_serializeContext)
{
Marshaler<AZ::u32> sizeMarshaler;
Marshaler<AZ::Uuid> uuidMarshaler;
AZStd::vector<AZ::u8> memoryBuffer(BufferSize);
// Start buffer in write mode.
AZ::IO::ByteContainerStream<decltype(memoryBuffer)> memoryStream(&memoryBuffer);
AZ::u32 bufferSize = 0;
if (m_serializeContext->FindClassData(value.m_typeId))
{
if (AZ::Utils::SaveObjectToStream(memoryStream, AZ::DataStream::StreamType::ST_BINARY, value.m_data, value.m_typeId, m_serializeContext))
{
bufferSize = static_cast<AZ::u32>(memoryStream.GetCurPos());
}
}
else
{
AZ_Error("DynamicSerializableFieldMarshaler", !value.IsValid(), "Could not save object to stream because type Id %s is not registered with the serializer.\n", value.m_typeId.ToString<AZStd::string>().c_str());
}
sizeMarshaler.Marshal(wb, bufferSize);
uuidMarshaler.Marshal(wb, value.m_typeId);
wb.WriteRaw(memoryBuffer.data(), bufferSize);
}
}
AZ_FORCE_INLINE void Unmarshal(AZ::DynamicSerializableField& value, ReadBuffer& rb) const
{
value.DestroyData(m_serializeContext);
AZ_Error("DynamicSerializableFieldMarshaler", m_serializeContext, "Unknown SerializationContext. Aborting Unmarshal attempt.\n");
if (m_serializeContext)
{
Marshaler<AZ::u32> sizeMarshaler;
AZ::u32 marshaledBufferSize = 0;
sizeMarshaler.Unmarshal(marshaledBufferSize, rb);
AZ_Assert(marshaledBufferSize <= BufferSize,"Trying to deserialize too much data for the allocated buffer size\n");
// Marshal out the TypeId so I can use it on the receiving end.
Marshaler<AZ::Uuid> uuidMarshaler;
uuidMarshaler.Unmarshal(value.m_typeId, rb);
if (marshaledBufferSize > 0)
{
// See if there's some nice way to use this.
// - Can't make this a member variable, since both these methods are const.
AZStd::vector<AZ::u8> memoryBuffer(marshaledBufferSize + 1);
if (rb.ReadRaw(memoryBuffer.data(), marshaledBufferSize))
{
// Start buffer in read mode.
AZ::IO::ByteContainerStream<decltype(memoryBuffer)> memoryStream(&memoryBuffer);
// we'll use a strict filter here, one that doesn't allow deserialization to automatically start loading assets, nor tolerates errors.
// this is becuase this is coming from a network interface and should always be error-free.
AZ::ObjectStream::FilterDescriptor filterToUse(&AZ::Data::AssetFilterNoAssetLoading, AZ::ObjectStream::FILTERFLAG_STRICT);
value.m_data = AZ::Utils::LoadObjectFromStream(memoryStream, m_serializeContext, &value.m_typeId, filterToUse);
}
}
}
}
private:
AZ::SerializeContext* m_serializeContext;
};
/**
* Specialized marshaler for AZ::DynamicSerializableField
* Mainly here to hook into the DataSet Marshaler auto detection logic, and provide a default buffer size for the actual marshaler
*/
template<>
class Marshaler<AZ::DynamicSerializableField>
: public DynamicSerializableFieldMarshaler<1024>
{
public:
Marshaler()
{
}
// Mainly here for unit test purposes.
Marshaler(AZ::SerializeContext* context)
: DynamicSerializableFieldMarshaler(context)
{
}
};
}
#endif
@@ -1,76 +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
#ifndef AZFRAMEWORK_NETWORK_ENTITYIDMARSHALER_H
#define AZFRAMEWORK_NETWORK_ENTITYIDMARSHALER_H
#include <AzCore/Component/EntityId.h>
#include <AzCore/Component/NamedEntityId.h>
#include <GridMate/Serialize/ContainerMarshal.h>
#include <GridMate/Serialize/DataMarshal.h>
namespace GridMate
{
template<>
class Marshaler<AZ::EntityId>
{
public:
AZ_TYPE_INFO_LEGACY( Marshaler, "{23F4722F-D104-4E30-9342-43F4DDD1894D}", AZ::EntityId );
void Marshal(GridMate::WriteBuffer& wb, const AZ::EntityId& source) const
{
Marshaler<AZ::u64> idMarshaler;
idMarshaler.Marshal(wb,static_cast<AZ::u64>(source));
}
void Unmarshal(AZ::EntityId& target, GridMate::ReadBuffer& rb) const
{
AZ::u64 id = 0;
Marshaler<AZ::u64> idMarshaler;
idMarshaler.Unmarshal(id,rb);
target = AZ::EntityId(id);
}
};
template<>
class Marshaler<AZ::NamedEntityId>
{
public:
void Marshal(GridMate::WriteBuffer& wb, const AZ::NamedEntityId& source) const
{
Marshaler<AZ::u64> idMarshaler;
idMarshaler.Marshal(wb, static_cast<AZ::u64>(source));
Marshaler<AZStd::string> stringMarshaler;
stringMarshaler.Marshal(wb, source.GetName());
}
void Unmarshal(AZ::NamedEntityId& target, GridMate::ReadBuffer& rb) const
{
AZ::u64 id = 0;
Marshaler<AZ::u64> idMarshaler;
idMarshaler.Unmarshal(id, rb);
AZStd::string name;
Marshaler<AZStd::string> stringMarshaler;
stringMarshaler.Unmarshal(name, rb);
target = AZ::NamedEntityId(AZ::EntityId(id), name);
}
};
}
#endif
@@ -1,187 +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.
*
*/
#include <AzFramework/Network/InterestManagerComponent.h>
#include <AzCore/Memory/AllocationRecords.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <GridMate/GridMate.h>
#include <GridMate/Replica/Interest/BitmaskInterestHandler.h>
#include <GridMate/Replica/Interest/InterestManager.h>
#include <GridMate/Replica/Interest/ProximityInterestHandler.h>
using namespace GridMate;
namespace AzFramework
{
void InterestManagerComponent::Reflect(AZ::ReflectContext* context)
{
if (context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<InterestManagerComponent, AZ::Component>()
->Version(1);
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<InterestManagerComponent>(
"InterestManagerComponent", "Interest manager instance")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b));
}
}
// We need to register the chunk types for each handler here at reflect time
if (!GridMate::ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(GridMate::ReplicaChunkClassId(ProximityInterestChunk::GetChunkName())))
{
GridMate::ReplicaChunkDescriptorTable::Get().RegisterChunkType<GridMate::ProximityInterestChunk>();
}
if (!GridMate::ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(GridMate::ReplicaChunkClassId(BitmaskInterestChunk::GetChunkName())))
{
GridMate::ReplicaChunkDescriptorTable::Get().RegisterChunkType<GridMate::BitmaskInterestChunk>();
}
}
}
void InterestManagerComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("InterestManager", 0x79993873));
}
void InterestManagerComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("InterestManager", 0x79993873));
}
InterestManagerComponent::InterestManagerComponent()
: m_im(nullptr)
, m_bitmaskHandler(nullptr)
, m_proximityHandler(nullptr)
, m_session(nullptr)
{
}
void InterestManagerComponent::Activate()
{
InterestManagerRequestsBus::Handler::BusConnect();
NetBindingSystemEventsBus::Handler::BusConnect();
AZ::SystemTickBus::Handler::BusConnect();
}
void InterestManagerComponent::Deactivate()
{
AZ::SystemTickBus::Handler::BusDisconnect();
NetBindingSystemEventsBus::Handler::BusDisconnect();
InterestManagerRequestsBus::Handler::BusDisconnect();
ShutdownInterestManager();
}
void InterestManagerComponent::OnSystemTick()
{
if (m_im && m_im->IsReady())
{
m_im->Update();
}
}
InterestManager* InterestManagerComponent::GetInterestManager()
{
return m_im.get();
}
BitmaskInterestHandler* InterestManagerComponent::GetBitmaskInterest()
{
return m_bitmaskHandler.get();
}
ProximityInterestHandler* InterestManagerComponent::GetProximityInterest()
{
return m_proximityHandler.get();
}
void InterestManagerComponent::OnNetworkSessionActivated(GridSession* session)
{
AZ_Assert(m_session == nullptr, "Already bound to the session");
AZ_TracePrintf("AzFramework", "Interest manager hooked up to the session '%s'\n", session->GetId().c_str());
m_session = session;
m_session->GetReplicaMgr()->SetAutoBroadcast(false);
InitInterestManager();
}
void InterestManagerComponent::OnNetworkSessionDeactivated(GridSession* session)
{
if (m_session && m_session == session)
{
AZ_TracePrintf("AzFramework", "Interest manager disconnected from the session '%s'\n", session ? session->GetId().c_str() : "nullptr");
if (m_session->GetReplicaMgr())
{
m_session->GetReplicaMgr()->SetAutoBroadcast(true);
}
m_session = nullptr;
ShutdownInterestManager();
}
else
{
AZ_Warning("AzFramework", false, "Interest manager was never active for session '%s'\n", session ? session->GetId().c_str() : "nullptr");
}
}
void InterestManagerComponent::InitInterestManager()
{
AZ_Assert(m_im == nullptr, "Already initialized interest manager");
m_im = AZStd::make_unique<InterestManager>();
InterestManagerDesc desc;
desc.m_rm = m_session->GetReplicaMgr();
m_im->Init(desc);
m_bitmaskHandler = AZStd::make_unique<BitmaskInterestHandler>();
m_im->RegisterHandler(m_bitmaskHandler.get());
m_proximityHandler = AZStd::make_unique<ProximityInterestHandler>();
m_im->RegisterHandler(m_proximityHandler.get());
InterestManagerEventsBus::Broadcast(
&InterestManagerEventsBus::Events::OnInterestManagerActivate, m_im.get());
}
void InterestManagerComponent::ShutdownInterestManager()
{
if (m_im)
{
InterestManagerEventsBus::Broadcast(
&InterestManagerEventsBus::Events::OnInterestManagerDeactivate, m_im.get());
m_im->UnregisterHandler(m_bitmaskHandler.get());
m_im->UnregisterHandler(m_proximityHandler.get());
m_bitmaskHandler = nullptr;
m_proximityHandler = nullptr;
m_im = nullptr;
}
}
} // namespace AzFramework
@@ -1,120 +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.
*
*/
#ifndef AZFRAMEWORK_NET_INTERESTMANAGER_COMPONENT_H
#define AZFRAMEWORK_NET_INTERESTMANAGER_COMPONENT_H
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Network/NetBindingSystemBus.h>
#include <GridMate/Session/Session.h>
namespace GridMate
{
class InterestManager;
class GridSession;
class BitmaskInterestHandler;
class ProximityInterestHandler;
}
namespace AzFramework
{
class InterestManagerSystemRequests
: public AZ::EBusTraits
{
public:
virtual ~InterestManagerSystemRequests() {}
// Returns interest manager instance
virtual GridMate::InterestManager* GetInterestManager() = 0;
// Returns interest manager instance
virtual GridMate::BitmaskInterestHandler* GetBitmaskInterest() = 0;
// Returns interest manager instance
virtual GridMate::ProximityInterestHandler* GetProximityInterest() = 0;
};
// Interface Bus
using InterestManagerRequestsBus = AZ::EBus<InterestManagerSystemRequests>;
class InterestManagerEvents
: public AZ::EBusTraits
{
public:
virtual ~InterestManagerEvents() {}
// Called when interest manager is initialized and ready to use
virtual void OnInterestManagerActivate(GridMate::InterestManager* im) { (void)im; }
// Called when interest manager is deactivated
virtual void OnInterestManagerDeactivate(GridMate::InterestManager* im) { (void)im; }
};
// Interface Bus
using InterestManagerEventsBus = AZ::EBus<InterestManagerEvents>;
/**
* Interest manager component.
* When component is activated replicas will go through interest filtering before being sent to other peers
*/
class InterestManagerComponent
: public AZ::Component
, public AZ::SystemTickBus::Handler
, public InterestManagerRequestsBus::Handler
, public NetBindingSystemEventsBus::Handler
{
public:
AZ_COMPONENT(InterestManagerComponent, "{55371FA7-2942-4A3C-A3EA-27FF2C7DB6C5}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
InterestManagerComponent();
void Activate() override;
void Deactivate() override;
protected:
// AZ::SystemTickBus::Listener interface implementation
void OnSystemTick() override;
// InterestManagerSystemRequests implementation
GridMate::InterestManager* GetInterestManager() override;
GridMate::BitmaskInterestHandler* GetBitmaskInterest() override;
GridMate::ProximityInterestHandler* GetProximityInterest() override;
// SessionEventBus
void OnNetworkSessionActivated(GridMate::GridSession* session) override;
void OnNetworkSessionDeactivated(GridMate::GridSession* session) override;
void InitInterestManager();
void ShutdownInterestManager();
// Interest handlers
AZStd::unique_ptr<GridMate::InterestManager> m_im;
AZStd::unique_ptr<GridMate::BitmaskInterestHandler> m_bitmaskHandler;
AZStd::unique_ptr<GridMate::ProximityInterestHandler> m_proximityHandler;
GridMate::GridSession* m_session; ///< currently bound session
private:
InterestManagerComponent(const InterestManagerComponent&) = delete; //Cannot use default due to unique_ptr.
};
} // namesapce AzFramework
#endif // AZFRAMEWORK_NET_INTERESTMANAGER_COMPONENT_H
@@ -1,111 +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.
*
*/
#include <AzFramework/Network/NetBindable.h>
#include <AzFramework/Network/NetBindingHandlerBus.h>
#include <AzFramework/Network/NetSystemBus.h>
#include <AzFramework/Network/NetworkContext.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
namespace AzFramework
{
////////////////
// NetBindable
////////////////
NetBindable::NetBindable()
: m_isSyncEnabled(true)
{
}
NetBindable::~NetBindable()
{
if (m_chunk)
{
// NetBindable is a base class for handlers for replica chunks, so we have to clear the handler since this object is about to go away
m_chunk->SetHandler(nullptr);
m_chunk = nullptr;
}
}
GridMate::ReplicaChunkPtr NetBindable::GetNetworkBinding()
{
NetworkContext* netContext = nullptr;
NetSystemRequestBus::BroadcastResult(netContext, &NetSystemRequestBus::Events::GetNetworkContext);
AZ_Assert(netContext, "Cannot bind objects to the network with no NetworkContext");
if (netContext)
{
m_chunk = netContext->CreateReplicaChunk(azrtti_typeid(this));
netContext->Bind(this, m_chunk, NetworkContextBindMode::Authoritative);
return m_chunk;
}
return nullptr;
}
void NetBindable::SetNetworkBinding (GridMate::ReplicaChunkPtr chunk)
{
m_chunk = chunk;
NetworkContext* netContext = nullptr;
NetSystemRequestBus::BroadcastResult(netContext, &NetSystemRequestBus::Events::GetNetworkContext);
AZ_Assert(netContext, "Cannot bind objects to the network with no NetworkContext");
if (netContext)
{
netContext->Bind(this, m_chunk, NetworkContextBindMode::NonAuthoritative);
}
}
void NetBindable::UnbindFromNetwork()
{
if (m_chunk)
{
// NetworkContext-reflected chunks need access to the handler when they are being destroyed, so we won't null handler in here
m_chunk = nullptr;
}
}
void NetBindable::NetInit()
{
NetworkContext* netContext = nullptr;
NetSystemRequestBus::BroadcastResult(netContext, &NetSystemRequestBus::Events::GetNetworkContext);
AZ_Assert(netContext, "Cannot bind objects to the network with no NetworkContext");
if (netContext)
{
netContext->Bind(this, nullptr, NetworkContextBindMode::NonAuthoritative);
}
}
void NetBindable::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<NetBindable>()
->Field("m_isSyncEnabled", &NetBindable::m_isSyncEnabled);
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<NetBindable>(
"Network Bindable", "Network-bindable components are synchronized over the network.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Networking")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->DataElement(AZ::Edit::UIHandlers::Default, &NetBindable::m_isSyncEnabled, "Bind To network", "Enable binding to the network.");
}
}
}
}
@@ -1,799 +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.
*
*/
#ifndef AZFRAMEWORK_NET_BINDABLE_H
#define AZFRAMEWORK_NET_BINDABLE_H
#include <AzCore/Component/EntityId.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/list.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <GridMate/Replica/ReplicaCommon.h>
#include <GridMate/Replica/ReplicaChunkInterface.h>
#include <GridMate/Replica/DataSet.h>
#include <GridMate/Replica/RemoteProcedureCall.h>
/*
* Including common GridMate marshallers.
* Otherwise, users of NetBindable/NetworkContext have to find and include them themselves.
*/
#include <AzFramework/Network/EntityIdMarshaler.h>
#include <GridMate/Serialize/MathMarshal.h>
#include <GridMate/Serialize/CompressionMarshal.h>
#include <GridMate/Serialize/ContainerMarshal.h>
#include <GridMate/Serialize/DataMarshal.h>
#include <GridMate/Serialize/UtilityMarshal.h>
#include <GridMate/Serialize/UuidMarshal.h>
namespace AZ
{
class ReflectContext;
namespace Internal
{
template <class FieldType>
class AzFrameworkNetBindableFieldContainer;
}
}
namespace AzFramework
{
using GridMate::DataSetBase;
using GridMate::DataSet;
using GridMate::Marshaler;
using GridMate::BasicThrottle;
using GridMate::RpcBase;
using GridMate::TimeContext;
using GridMate::RpcContext;
using GridMate::RpcDefaultTraits;
enum class NetworkContextBindMode
{
Authoritative,
NonAuthoritative
};
/**
* Components that want to be synchronized over the network should implement NetBindable.
* The NetBindable interface is obtained via AZ_RTTI so components need to make sure to
* declare NetBindable as a base class in their AZ_RTTI declaration (or AZ_COMPONENT declaration),
* as well as to declare both AZ::Component and NetBindable as base classes in the reflection.
*
* For example, here is how to mark a component for network replication in its class declaration:
*
* class TestFieldComponent
* : public AZ::Component
* , public AzFramework::NetBindable
* {
* public:
* AZ_COMPONENT(TestFieldComponent, "{DD02A926-F6B3-4820-9587-62EED9EEBB3F}", NetBindable);
*
* static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
* {
* required.push_back(AZ_CRC("ReplicaChunkService"));
* }
*
* Note, you should declare a dependency on NetBindingComponent as it is done above with "ReplicaChunkService."
* NetBindingComponent is required for an entity to be considered for network replication and replicate your NetBindable-components.
*/
class NetBindable
: public GridMate::ReplicaChunkInterface
{
public:
AZ_RTTI(NetBindable, "{80206665-D429-4703-B42E-94434F82F381}");
NetBindable();
virtual ~NetBindable();
void NetInit();
//! Called during network binding on the master. The default implementation will use the
//! NetworkContext to create a chunk. User implementations should create and return a new binding.
virtual GridMate::ReplicaChunkPtr GetNetworkBinding();
//! Called during network binding on proxies.
virtual void SetNetworkBinding(GridMate::ReplicaChunkPtr chunk);
//! Called when network is unbound. Implementations should release their references to the binding, if they held a reference.
virtual void UnbindFromNetwork();
static void Reflect(AZ::ReflectContext* reflection);
template <class DataType, typename MarshalerType = Marshaler<DataType>, typename ThrottlerType = BasicThrottle<DataType> >
class Field;
template <class DataType, class InterfaceType, void (InterfaceType::*)(const DataType&, const TimeContext&), typename MarshalerType = Marshaler<DataType>, typename ThrottlerType = BasicThrottle<DataType> >
class BoundField;
template <typename ... Args>
class Rpc;
inline bool IsSyncEnabled() const { return m_isSyncEnabled; }
//! Can be used to disabled net sync on a per component basis
inline void SetSyncEnabled(bool enabled) { m_isSyncEnabled = enabled; }
protected:
bool m_isSyncEnabled;
GridMate::ReplicaChunkPtr m_chunk = nullptr;
};
class NetBindableFieldBase
{
public:
virtual ~NetBindableFieldBase() = default;
virtual void Bind(DataSetBase* dataSet, NetworkContextBindMode mode) = 0;
};
/**
* \brief NetBindable provides a simplified network interface to mark a member variable inside AZ::Component
* as a network field that will be replicated by GridMate.
*
* \tparam DataType data type of the field, can be either a common C++ type or a custom type
* \tparam MarshalerType optional, marshaler type that provides custom marshal and unmarshal logic, i.e. how to write @DataType to the network and back, see @GridMate::Marshaler
* \tparam ThrottlerType optional, throttler provides the ability to detect if a value is to be considered changed significantly enough for GridMate to replicate its state, see @GridMate::BasicThrottle
*
* Example:
*
* class TestFieldComponent : public AZ::Component , public AzFramework::NetBindable
* {
* public:
* Field<int> m_testInt;
*
* And it must be reflected to SerializeContext _and_ NetworkContext:
*
* void TestFieldComponent::Reflect(AZ::ReflectContext* context)
* {
* if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
* {
* serialize->Class<TestFieldComponent, AZ::Component, AzFramework::NetBindable>()
* ->Field("Test Int", &TestFieldComponent::m_testInt)
* ->Version(1);
* }
*
* if (AzFramework::NetworkContext* net = azrtti_cast<AzFramework::NetworkContext*>(context))
* {
* net->Class<TestFieldComponent>()
* ->Field("Test Int", &TestFieldComponent::m_testInt);
* }
* }
*
* Then you can simply write to it as it was an integer:
*
* m_testInt = 3;
* // or
* m_testInt = *m_testInt + 1;
*/
template <class DataType, typename MarshalerType, typename ThrottlerType>
class NetBindable::Field
: public NetBindableFieldBase
{
friend class AZ::Internal::AzFrameworkNetBindableFieldContainer<NetBindable::Field<DataType, MarshalerType, ThrottlerType> >;
public:
using DataSetType = DataSet<DataType, MarshalerType, ThrottlerType>;
using ValueType = DataType;
explicit Field(const DataType& value = DataType())
: m_dataSet(nullptr)
, m_value(value)
{}
~Field() override = default;
/*
* Disabling copy and move constructors in order to allow for a common use of fields, for example:
* m_field = m_field + 1;
*/
Field (const Field& other) = delete;
Field (Field&& other) = delete;
Field& operator= (const Field& other) = delete;
Field& operator= (Field&& other) = delete;
const DataType& Get() const
{
return m_dataSet ? m_dataSet->Get() : m_value;
}
virtual operator const DataType&() const
{
return Get();
}
virtual const DataType& operator*() const
{
return Get();
}
virtual Field& operator=(const DataType& val)
{
if (m_dataSet)
{
m_dataSet->Set(val);
}
else
{
m_value = val;
}
return *this;
}
virtual Field& operator=(const DataType&& val)
{
if (m_dataSet)
{
m_dataSet->Set(AZStd::forward<const DataType>(val));
}
else
{
m_value = AZStd::move(val);
}
return *this;
}
void Bind(DataSetBase* dataSet, NetworkContextBindMode mode) override
{
BindDataSet(static_cast<DataSetType*>(dataSet), mode);
}
static void ConstructDataSet(void* mem, const char* name)
{
new (mem) DataSetType(name, DataType(), MarshalerType(), ThrottlerType());
}
static void DestructDataSet(void* mem)
{
DataSetType* dataSet = reinterpret_cast<DataSetType*>(mem);
dataSet->~DataSetType();
}
protected:
template <class DST>
void BindDataSet(DST* dataSet, NetworkContextBindMode mode)
{
if (m_dataSet)
{
m_value = m_dataSet->Get();
}
m_dataSet = dataSet;
if (m_dataSet)
{
if (mode == NetworkContextBindMode::Authoritative)
{
/*
* If we are binding Field<> or BoundField<> on a component of an authoritative entity,
* then we want to bring over the value of the field in the component. This occurs during GetNetworkBinding().
*
* Whereas on a client's (non-authoritative entities and their components) dataSet already has the desired value
* and should not be overwritten here.
*/
m_dataSet->Set(AZStd::move(m_value));
}
m_value = DataType();
}
}
DataType* CacheValue()
{
if (m_dataSet)
{
m_value = m_dataSet->Get();
}
return &m_value;
}
const DataType& GetCachedValue() const
{
return m_value;
}
private:
DataSet<DataType, MarshalerType, ThrottlerType>* m_dataSet;
DataType m_value;
};
/**
* \brief An extension of @NetBindable::Field with an ability to invoke a callback whenever the value changes on both authoritative and non-authoritative components.
* Or in other terms, on both the server and clients (when GridMate is setup to run in server-authoritative mode).
*
* \tparam DataType data type, same as @NetBindable::Field
* \tparam InterfaceType Component type class that holds this @BoundField
* \tparam FuncPtr member function pointer to the callback to invoke when this value is updated on non-authoritative components.
* \tparam MarshalerType optional, same as @NetBindable::Field
* \tparam ThrottlerType optional, same as @NetBindable::Field
*
* Example:
*
* BoundField<int, TestBoundFieldComponent, &TestBoundFieldComponent::OnBoundFieldChanged> m_testInt;
*
* And it must be reflected to SerializeContext _and_ NetworkContext just like @NetBindable::Field
*
* void TestFieldComponent::Reflect(AZ::ReflectContext* context)
* {
* if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
* {
* serialize->Class<TestFieldComponent, AZ::Component, AzFramework::NetBindable>()
* ->Field("Test Int", &TestFieldComponent::m_testInt)
* ->Version(1);
* }
*
* if (AzFramework::NetworkContext* net = azrtti_cast<AzFramework::NetworkContext*>(context))
* {
* net->Class<TestFieldComponent>()
* ->Field("Test Int", &TestFieldComponent::m_testInt);
* }
* }
*/
template <class DataType, class InterfaceType, void (InterfaceType::* FuncPtr)(const DataType&, const TimeContext&), typename MarshalerType, typename ThrottlerType>
class NetBindable::BoundField
: public NetBindable::Field<DataType, MarshalerType, ThrottlerType>
{
using BaseClass = NetBindable::Field<DataType, MarshalerType, ThrottlerType>;
friend class AZ::Internal::AzFrameworkNetBindableFieldContainer<NetBindable::BoundField<DataType, InterfaceType, FuncPtr, MarshalerType, ThrottlerType> >;
public:
AZ_TYPE_INFO_LEGACY(BoundField, "{5151CEAF-6AC0-45D7-AEDF-8B6C46CE07B9}", DataType, InterfaceType, MarshalerType, ThrottlerType);
using DataSetType = typename DataSet<DataType, MarshalerType, ThrottlerType>::template BindInterface<InterfaceType, FuncPtr, GridMate::DataSetInvokeEverywhereTraits>;
explicit BoundField(const DataType& value = DataType())
: BaseClass(value)
{}
~BoundField() override = default;
/*
* Disabling copy and move constructors in order to allow for a common use of fields, for example:
* m_field = m_field + 1;
*/
BoundField (const BoundField& other) = delete;
BoundField (BoundField&& other) = delete;
BoundField& operator= (const BoundField& other) = delete;
BoundField& operator= (BoundField&& other) = delete;
operator DataType() const
{
return BaseClass::Get();
}
const DataType& operator*() const override
{
return BaseClass::Get();
}
BaseClass& operator=(const DataType& val) override
{
BaseClass::operator=(val);
return *this;
}
BaseClass& operator=(const DataType&& val) override
{
BaseClass::operator=(val);
return *this;
}
void Bind(DataSetBase* dataSet, NetworkContextBindMode mode) override
{
BaseClass::BindDataSet(static_cast<DataSetType*>(dataSet), mode);
}
static void ConstructDataSet(void* mem, const char* name)
{
new (mem) DataSetType(name, DataType(), MarshalerType(), ThrottlerType());
}
static void DestructDataSet(void* mem)
{
DataSetType* dataSet = reinterpret_cast<DataSetType*>(mem);
dataSet->~DataSetType();
}
};
class NetBindableRpcBase
{
public:
virtual ~NetBindableRpcBase() = default;
virtual void Bind(RpcBase* rpc) = 0;
virtual void Bind(NetBindable* handler) = 0;
};
/**
* \brief NetBindable::Rpc::Binder should be used for any RPC in a NetBindable that you want
* to be able to call remotely. If the object is not network bound, RPC
* calls will dispatch directly, as if the object was authoritative.
*
* \tparam Args any custom parameters for the remote procedure calls.
*
* Here is an example:
*
* // callback
* bool OnRpc(float value, const GridMate::RpcContext& rc);
*
* // Rpc declaration
* Rpc<float>::Binder<TestRPCComponent, &TestRPCComponent::OnRpc> m_testRpc;
*
* Rpc needs to be reflected in NetworkContext like this:
*
* void TestRPCComponent::Reflect(AZ::ReflectContext* context)
* {
* if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
* {
* serialize->Class<TestRPCComponent, AZ::Component, AzFramework::NetBindable>()
* ->Version(1);
* }
*
* if (AzFramework::NetworkContext* net = azrtti_cast<AzFramework::NetworkContext*>(context))
* {
* net->Class<TestRPCComponent>()
* ->RPC("Test RPC", &TestRPCComponent::m_testRpc);
* }
* }
*
* It can be invoked as if it was a method:
*
* m_testRpc(deltaTime);
*/
template <typename ... Args>
class NetBindable::Rpc
{
public:
/**
* \brief Binds rpc callback to a pointer to member function of AZ::Component derived from AzFramework::NetBindable
* See @NetBindable::Rpc
*/
template<class InterfaceType, bool (InterfaceType::* FuncPtr)(Args..., const RpcContext&), class Traits = RpcDefaultTraits>
class Binder
: public NetBindableRpcBase
{
friend class NetworkContext;
public:
using BindInterfaceType = typename GridMate::Rpc<GridMate::RpcArg<Args>...>::template BindInterface<InterfaceType, FuncPtr, Traits>;
Binder()
: m_rpc(nullptr)
, m_instance(nullptr)
{}
void Bind(RpcBase* rpc) override
{
m_rpc = static_cast<BindInterfaceType*>(rpc);
m_instance = nullptr;
}
void Bind(NetBindable* bindable) override
{
m_instance = static_cast<InterfaceType*>(bindable);
m_rpc = nullptr;
}
template <typename ... CallArgs>
void operator()(CallArgs&& ... args)
{
AZ_Assert(m_instance || m_rpc, "Cannot call an RPC without either a local instance or a network bound handler, did you forget to register with NetworkContext()?");
if (m_rpc) // connected to network
{
(*m_rpc)(AZStd::forward<CallArgs>(args) ...);
}
else if (m_instance) // local dispatch
{
(*m_instance.*FuncPtr)(AZStd::forward<CallArgs>(args) ..., RpcContext());
}
}
protected:
static void ConstructRpc(void* mem, const char* name)
{
new (mem) BindInterfaceType(name);
}
static void DestructRpc(void*) { }
private:
BindInterfaceType* m_rpc;
InterfaceType* m_instance;
};
Rpc() = delete;
};
} // namespace AzFramework
namespace AZ
{
AZ_TYPE_INFO_TEMPLATE_WITH_NAME(AzFramework::NetBindable::Field, "Field", "{00D56FA7-F8BD-402B-97FB-0E2599897056}", AZ_TYPE_INFO_CLASS, AZ_TYPE_INFO_TYPENAME, AZ_TYPE_INFO_TYPENAME);
namespace Internal
{
template <class FieldType>
class AzFrameworkNetBindableFieldContainer
: public SerializeContext::IDataContainer
{
using ValueType = typename FieldType::ValueType;
public:
AzFrameworkNetBindableFieldContainer()
{
m_classElement.m_name = GetDefaultElementName();
m_classElement.m_nameCrc = GetDefaultElementNameCrc();
m_classElement.m_dataSize = sizeof(ValueType);
m_classElement.m_offset = 0;
m_classElement.m_azRtti = GetRttiHelper<ValueType>();
m_classElement.m_flags = AZStd::is_pointer<ValueType>::value ? SerializeContext::ClassElement::FLG_POINTER : 0;
m_classElement.m_genericClassInfo = SerializeGenericTypeInfo<ValueType>::GetGenericInfo();
m_classElement.m_typeId = SerializeGenericTypeInfo<ValueType>::GetClassTypeId();
m_classElement.m_editData = nullptr;
}
/// Returns the element generic (offsets are mostly invalid 0xbad0ffe0, there are exceptions). Null if element with this name can't be found.
virtual const SerializeContext::ClassElement* GetElement(AZ::u32 elementNameCrc) const override
{
if (elementNameCrc == m_classElement.m_nameCrc)
{
return &m_classElement;
}
return nullptr;
}
bool GetElement(SerializeContext::ClassElement& classElement, const SerializeContext::DataElement& dataElement) const override
{
if (dataElement.m_nameCrc == m_classElement.m_nameCrc)
{
classElement = m_classElement;
return true;
}
return false;
}
/// Enumerate elements in the array
virtual void EnumElements(void* instance, const ElementCB& cb) override
{
FieldType* field = reinterpret_cast<FieldType*>(instance);
// We can't mess with the internal storage of the dataset safely, so we copy it into
// the field's local value cache temporarily, then hand that to the callback
// This will modify the local value cache, but that shouldn't matter as it will never
// be used as long as a dataset is bound
// If this turns out to be a perf problem due to copies of complex types, then
// the easy solution is to get DataSets to expose a pointer to their underlying
// data storage, and then we can return a pointer to that and modify it directly
// if the field is bound to the network
ValueType* valPtr = field->CacheValue();
cb(valPtr, m_classElement.m_typeId, m_classElement.m_genericClassInfo ? m_classElement.m_genericClassInfo->GetClassData() : nullptr, &m_classElement);
// Ensure that the dataset is updated if changes happened
*field = *valPtr;
}
void EnumTypes(const ElementTypeCB& cb) override
{
cb(m_classElement.m_typeId, &m_classElement);
}
/// Return number of elements in the container.
virtual size_t Size(void*) const override
{
return 1;
}
/// Returns the capacity of the container. Returns 0 for objects without fixed capacity.
virtual size_t Capacity(void* instance) const override
{
(void)instance;
return 1;
}
/// Returns true if elements pointers don't change on add/remove. If false you MUST enumerate all elements.
virtual bool IsStableElements() const override { return true; }
/// Returns true if the container is fixed size, otherwise false.
virtual bool IsFixedSize() const override { return true; }
/// Returns if the container is fixed capacity, otherwise false
virtual bool IsFixedCapacity() const override { return true; }
/// Returns true if the container is a smart pointer.
virtual bool IsSmartPointer() const override { return true; }
/// Returns true if the container elements can be addressed by index, otherwise false.
virtual bool CanAccessElementsByIndex() const override { return false; }
/// Reserve element
virtual void* ReserveElement(void* instance, const SerializeContext::ClassElement*) override
{
FieldType* field = reinterpret_cast<FieldType*>(instance);
*field = ValueType();
return field->CacheValue(); // return the local value, should be accurate as the field will be unbound at serialization time
}
/// Get an element's address by its index (called before the element is loaded).
virtual void* GetElementByIndex(void*, const SerializeContext::ClassElement*, size_t) override
{
return nullptr;
}
/// Store element
virtual void StoreElement(void* instance, void*) override
{
// force store the value again, just in case the field is bound to a dataset
FieldType* field = reinterpret_cast<FieldType*>(instance);
*field = field->GetCachedValue();
}
/// Remove element in the container.
virtual bool RemoveElement(void* instance, const void*, SerializeContext*) override
{
FieldType* field = reinterpret_cast<FieldType*>(instance);
*field = ValueType();
return false; // you can't remove element from this container.
}
/// Remove elements (removed array of elements) regardless if the container is Stable or not (IsStableElements)
virtual size_t RemoveElements(void* instance, const void**, size_t, SerializeContext*) override
{
RemoveElement(instance, nullptr, nullptr);
return 0; // you can't remove elements from this container.
}
/// Clear elements in the instance.
virtual void ClearElements(void* instance, SerializeContext*) override
{
RemoveElement(instance, nullptr, nullptr);
}
SerializeContext::ClassElement m_classElement; ///< Generic class element covering as must as possible of the element (offset, and some other fields are invalid)
};
}
template <class DataType, typename MarshalerType, typename ThrottlerType>
struct SerializeGenericTypeInfo< AzFramework::NetBindable::Field<DataType, MarshalerType, ThrottlerType> >
{
typedef typename AzFramework::NetBindable::Field<DataType, MarshalerType, ThrottlerType> ContainerType;
class GenericClassNetBindableField
: public GenericClassInfo
{
public:
AZ_TYPE_INFO(GenericClassNetBindableField, "{C1D4DD97-5DD7-42ED-969C-7435F27F5D8C}");
GenericClassNetBindableField()
: m_classData{ SerializeContext::ClassData::Create<ContainerType>("AzFramework::NetBindable::Field", GetSpecializedTypeId(), Internal::NullFactory::GetInstance(), nullptr, &m_containerStorage) }
{
}
SerializeContext::ClassData* GetClassData() override
{
return &m_classData;
}
size_t GetNumTemplatedArguments() override
{
return 1;
}
const Uuid& GetTemplatedTypeId(size_t) override
{
return SerializeGenericTypeInfo<DataType>::GetClassTypeId();
}
const Uuid& GetSpecializedTypeId() const override
{
return azrtti_typeid<ContainerType>();
}
const Uuid& GetGenericTypeId() const override
{
return TYPEINFO_Uuid();
}
const Uuid& GetLegacySpecializedTypeId() const override
{
return AZ::AzTypeInfo<ContainerType>::template Uuid<AZ::PointerRemovedTypeIdTag>();
}
void Reflect(SerializeContext* serializeContext)
{
if (serializeContext)
{
serializeContext->RegisterGenericClassInfo(GetSpecializedTypeId(), this, &AnyTypeInfoConcept<ContainerType>::CreateAny);
if (GenericClassInfo* containerGenericClassInfo = m_containerStorage.m_classElement.m_genericClassInfo)
{
containerGenericClassInfo->Reflect(serializeContext);
}
}
}
protected:
Internal::AzFrameworkNetBindableFieldContainer<ContainerType> m_containerStorage;
SerializeContext::ClassData m_classData;
};
using ClassInfoType = GenericClassNetBindableField;
static ClassInfoType* GetGenericInfo()
{
return GetCurrentSerializeContextModule().CreateGenericClassInfo<ContainerType>();
}
static const Uuid& GetClassTypeId()
{
return GetGenericInfo()->GetClassData()->m_typeId;
}
};
template <class DataType, class InterfaceType, void (InterfaceType::* FuncPtr)(const DataType&, const AzFramework::TimeContext&), typename MarshalerType, typename ThrottlerType>
struct SerializeGenericTypeInfo< typename AzFramework::NetBindable::BoundField<DataType, InterfaceType, FuncPtr, MarshalerType, ThrottlerType> >
{
typedef typename AzFramework::NetBindable::BoundField<DataType, InterfaceType, FuncPtr, MarshalerType, ThrottlerType> ContainerType;
class GenericClassNetBindableBoundField
: public GenericClassInfo
{
public:
AZ_TYPE_INFO(GenericClassNetBindableBoundField, "{EFD64FE7-9432-401A-B7A1-1767F4C5A7F0}");
GenericClassNetBindableBoundField()
: m_classData{ SerializeContext::ClassData::Create<ContainerType>("AzFramework::NetBindable::BoundField", GetSpecializedTypeId(), Internal::NullFactory::GetInstance(), nullptr, &m_containerStorage) }
{
}
SerializeContext::ClassData* GetClassData() override
{
return &m_classData;
}
size_t GetNumTemplatedArguments() override
{
return 1;
}
const Uuid& GetTemplatedTypeId(size_t) override
{
return SerializeGenericTypeInfo<DataType>::GetClassTypeId();
}
const Uuid& GetSpecializedTypeId() const override
{
return azrtti_typeid<ContainerType>();
}
const Uuid& GetGenericTypeId() const override
{
return TYPEINFO_Uuid();
}
const Uuid& GetLegacySpecializedTypeId() const override
{
return AZ::AzTypeInfo<ContainerType>::template Uuid<AZ::PointerRemovedTypeIdTag>();
}
void Reflect(SerializeContext* serializeContext)
{
if (serializeContext)
{
serializeContext->RegisterGenericClassInfo(GetSpecializedTypeId(), this, &AnyTypeInfoConcept<ContainerType>::CreateAny);
if (GenericClassInfo* containerGenericClassInfo = m_containerStorage.m_classElement.m_genericClassInfo)
{
containerGenericClassInfo->Reflect(serializeContext);
}
}
}
protected:
Internal::AzFrameworkNetBindableFieldContainer<ContainerType> m_containerStorage;
SerializeContext::ClassData m_classData;
};
using ClassInfoType = GenericClassNetBindableBoundField;
static ClassInfoType* GetGenericInfo()
{
return GetCurrentSerializeContextModule().CreateGenericClassInfo<ContainerType>();
}
static const Uuid& GetClassTypeId()
{
return GetGenericInfo()->GetClassData()->m_typeId;
}
};
}
#endif // AZFRAMEWORK_NET_BINDABLE_H
#pragma once
@@ -1,287 +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.
*
*/
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Network/NetBindable.h>
#include <AzFramework/Network/NetBindingSystemBus.h>
#include <AzFramework/Network/NetBindingComponentChunk.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <GridMate/Replica/Replica.h>
#include <GridMate/Replica/ReplicaChunk.h>
#include <GridMate/Replica/ReplicaFunctions.h>
#include <GridMate/Replica/ReplicaChunkDescriptor.h>
namespace AzFramework
{
void NetBindingComponent::Reflect(AZ::ReflectContext* reflection)
{
NetBindable::Reflect(reflection);
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<NetBindingComponent, AZ::Component>()
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<NetBindingComponent>(
"Network Binding", "The Network Binding component marks an entity as able to be replicated across the network")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Networking")
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/NetBinding.svg")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/NetBinding.png")
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.aws.amazon.com/lumberyard/latest/userguide/component-network-binding.html")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c));
}
}
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(reflection);
if (behaviorContext)
{
behaviorContext->EBus<NetBindingHandlerBus>("NetBindingHandlerBus")
->Event("IsEntityBoundToNetwork", &NetBindingHandlerBus::Events::IsEntityBoundToNetwork)
->Event("IsEntityAuthoritative", &NetBindingHandlerBus::Events::IsEntityAuthoritative)
// Desired, but currently unsupported events.
// Seems to be an unsupported type(AZ::u16)
//->Event("SetReplicaPriority", &NetBindingHandlerBus::Events::SetReplicaPriority)
//->Event("GetReplicaPriority", &NetBindingHandlerBus::Events::GetReplicaPriority)
;
}
// We also need to register the chunk type, and this would be a good time to do so.
if (!GridMate::ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(GridMate::ReplicaChunkClassId(NetBindingComponentChunk::GetChunkName())))
{
GridMate::ReplicaChunkDescriptorTable::Get().RegisterChunkType<AzFramework::NetBindingComponentChunk>();
}
}
NetBindingComponent::NetBindingComponent()
: m_isLevelSliceEntity(false)
{
}
void NetBindingComponent::Activate()
{
NetBindingHandlerBus::Handler::BusConnect(GetEntityId());
if (!IsEntityBoundToNetwork())
{
bool shouldBind = false;
NetBindingSystemBus::BroadcastResult( shouldBind, &NetBindingSystemBus::Events::ShouldBindToNetwork);
if (shouldBind)
{
BindToNetwork(nullptr);
}
else
{
/*
* This is the Editor path. We still need to call NetBindable::NetInit() in order
* to initialize NetworkContext Fields and RPCs, so that they behave as
* authoritative in game editor mode. Without this call RPCs callbacks won't invoke inside the Editor.
* For example:
*
* static void Reflect(...)
* {
* NetworkContext->Class<MyNetworkComponent>()->RPC("my rpc", &MyNetworkComponent::m_myRpc);
* }
* ...
* m_myRpc(); // <--- will not invoke the callback inside the Editor unless NetInit() is called below.
*/
for (Component* component : GetEntity()->GetComponents())
{
if (NetBindable* netBindable = azrtti_cast<NetBindable*>(component))
{
netBindable->NetInit();
}
}
}
}
}
void NetBindingComponent::Deactivate()
{
NetBindingHandlerBus::Handler::BusDisconnect();
if (IsEntityBoundToNetwork())
{
static_cast<NetBindingComponentChunk*>(m_chunk.get())->SetBinding(nullptr);
if (m_chunk->IsMaster())
{
m_chunk->GetReplica()->Destroy();
}
m_chunk = nullptr;
}
}
bool NetBindingComponent::IsEntityBoundToNetwork()
{
return m_chunk && m_chunk->GetReplica();
}
bool NetBindingComponent::IsEntityAuthoritative()
{
return !m_chunk || m_chunk->IsMaster();
}
void NetBindingComponent::BindToNetwork(GridMate::ReplicaPtr bindTo)
{
AZ_Assert(!IsEntityBoundToNetwork(), "We shouldn't be bound to the network if the network is just starting!");
if (bindTo)
{
NetBindingComponentChunkPtr bindingChunk = bindTo->FindReplicaChunk<NetBindingComponentChunk>();
AZ_Assert(bindingChunk, "Can't find NetBindingComponentChunk!");
m_chunk = bindingChunk;
bindingChunk->SetBinding(this);
GridMate::Replica* replica = bindingChunk->GetReplica();
size_t nChunks = replica->GetNumChunks();
size_t nBindings = bindingChunk->m_bindMap.Get().size();
AZ_Assert(nChunks == nBindings, "Number of chunks received is not the same as the size of the bind map!");
nBindings = AZ::GetMin(nBindings, nChunks);
for (size_t i = 0; i < nBindings; ++i)
{
AZ::ComponentId bindToId = bindingChunk->m_bindMap.Get()[i];
if (bindToId != AZ::InvalidComponentId)
{
AZ::Component* component = GetEntity()->FindComponent(bindToId);
NetBindable* netBindable = azrtti_cast<NetBindable*>(component);
AZ_Assert(netBindable, "Can't find net bindable component with id %llu to be bound to chunk type %s!", bindToId, replica->GetChunkByIndex(i)->GetDescriptor()->GetChunkName());
if (netBindable && netBindable->IsSyncEnabled())
{
netBindable->SetNetworkBinding(replica->GetChunkByIndex(i));
}
}
}
}
else
{
GridMate::ReplicaPtr replica = GridMate::Replica::CreateReplica(GetEntity()->GetName().c_str());
NetBindingComponentChunk* chunk = GridMate::CreateReplicaChunk<NetBindingComponentChunk>();
m_chunk = chunk;
chunk->SetBinding(this);
replica->AttachReplicaChunk(chunk);
chunk->m_bindMap.Modify([&](AZStd::vector<AZ::ComponentId>& bindMap)
{
// Mark the chunks already in the replica as non-components.
bindMap.resize(replica->GetNumChunks(), AZ::InvalidComponentId);
// Collect the bindings and add the to the replica
AZ::Entity* entity = GetEntity();
for (Component* component : entity->GetComponents())
{
NetBindable* netBindable = azrtti_cast<NetBindable*>(component);
if (netBindable && netBindable->IsSyncEnabled())
{
GridMate::ReplicaChunkPtr bindingChunk = netBindable->GetNetworkBinding();
if (bindingChunk)
{
bindMap.push_back(component->GetId());
replica->AttachReplicaChunk(bindingChunk);
}
}
}
return true;
});
// Add replica to session replica manager (may be deferred)
NetBindingSystemBus::Broadcast( &NetBindingSystemBus::Events::AddReplicaMaster, GetEntity(), replica);
}
}
void NetBindingComponent::UnbindFromNetwork()
{
if (m_chunk)
{
for (Component* component : GetEntity()->GetComponents())
{
NetBindable* netBindable = azrtti_cast<NetBindable*>(component);
if (netBindable && netBindable->IsSyncEnabled())
{
netBindable->UnbindFromNetwork();
}
}
NetBindingComponentChunkPtr chunk = static_cast<NetBindingComponentChunk*>(m_chunk.get());
chunk->SetBinding(nullptr);
m_chunk = nullptr;
if (chunk->IsProxy())
{
EntityContextId contextId = EntityContextId::CreateNull();
EntityIdContextQueryBus::EventResult( contextId, GetEntityId(), &EntityIdContextQueryBus::Events::GetOwningContextId);
if (contextId.IsNull())
{
delete GetEntity();
}
else if (!IsLevelSliceEntity())
{
NetBindingSystemBus::Broadcast( &NetBindingSystemBus::Events::UnbindGameEntity, GetEntityId(), m_sliceInstanceId);
}
}
}
}
void NetBindingComponent::MarkAsLevelSliceEntity()
{
AZ_Assert(!IsEntityBoundToNetwork(), "MarkAsLevelSliceEntity() has to be called before the entity is bound to the network!");
m_isLevelSliceEntity = true;
}
void NetBindingComponent::SetSliceInstanceId(const AZ::SliceComponent::SliceInstanceId& sliceInstanceId)
{
m_sliceInstanceId = sliceInstanceId;
}
void NetBindingComponent::RequestEntityChangeOwnership(GridMate::PeerId peerId)
{
if (m_chunk && m_chunk->GetReplica())
{
m_chunk->GetReplica()->RequestChangeOwnership(peerId);
}
}
void NetBindingComponent::SetReplicaPriority(GridMate::ReplicaPriority replicaPriority)
{
if (m_chunk)
{
m_chunk->SetPriority(replicaPriority);
}
}
GridMate::ReplicaPriority NetBindingComponent::GetReplicaPriority() const
{
if (m_chunk && m_chunk->GetReplica())
{
return m_chunk->GetReplica()->GetPriority();
}
else
{
AZ_Error("NetBindingComponent",false,"Trying to gather ReplicaPriority without having a Replica.");
return GridMate::k_replicaPriorityLowest;
}
}
bool NetBindingComponent::IsLevelSliceEntity() const
{
return m_isLevelSliceEntity;
}
} // namespace AzFramework
@@ -1,85 +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.
*
*/
#ifndef AZFRAMEWORK_NET_BINDING_COMPONENT_H
#define AZFRAMEWORK_NET_BINDING_COMPONENT_H
#include <AzCore/Component/Component.h>
#include <AzFramework/Network/NetBindingHandlerBus.h>
namespace AzFramework
{
/**
* NetBindingComponent enables network synchronization for the entity.
* It works in conjunction with NetBindingComponentChunk and NetBindingSystemComponent
* to perform network binding and notifies other components on the entity to bind
* their ReplicaChunks via the NetBindable interface.
*
* Entities bound to proxy replicas will be automatically destroyed when they are
* unbound from the network.
*/
class NetBindingComponent
: public AZ::Component
, public NetBindingHandlerBus::Handler
{
friend class NetBindingComponentChunk;
public:
AZ_COMPONENT(NetBindingComponent, "{E9CA5D63-ED2D-4B59-B3C4-EBCD4A0013E4}", NetBindingHandlerInterface);
NetBindingComponent();
protected:
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ReplicaChunkService", 0xf86b88a8));
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("ReplicaChunkService", 0xf86b88a8));
}
///////////////////////////////////////////////////////////////////////
// AZ::Component
static void Reflect(AZ::ReflectContext* reflection);
void Activate() override;
void Deactivate() override;
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// NetBindingHandlerBus::Handler
void BindToNetwork(GridMate::ReplicaPtr bindTo) override;
void UnbindFromNetwork() override;
bool IsEntityBoundToNetwork() override;
bool IsEntityAuthoritative() override;
void MarkAsLevelSliceEntity() override;
void SetSliceInstanceId(const AZ::SliceComponent::SliceInstanceId& sliceInstanceId) override;
void RequestEntityChangeOwnership(GridMate::PeerId peerId = GridMate::InvalidReplicaPeerId) override;
void SetReplicaPriority(GridMate::ReplicaPriority replicaPriority) override;
GridMate::ReplicaPriority GetReplicaPriority() const override;
///////////////////////////////////////////////////////////////////////
//! Returns if the entity belongs to the level slice for binding purposes.
bool IsLevelSliceEntity() const;
//! Points to the NetBindingComponentChunk counterpart.
GridMate::ReplicaChunkPtr m_chunk;
bool m_isLevelSliceEntity;
AZ::SliceComponent::SliceInstanceId m_sliceInstanceId;
};
} // namespace AzFramework
#endif // AZFRAMEWORK_NET_BINDING_COMPONENT_H
#pragma once
@@ -1,254 +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.
*
*/
#include <AzFramework/Network/NetBindingComponentChunk.h>
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Network/NetBindingSystemBus.h>
#include <AzFramework/Network/NetBindingEventsBus.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzFramework/Slice/SliceEntityBus.h>
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Serialize/UuidMarshal.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/IO/ByteContainerStream.h>
namespace AzFramework
{
NetBindingComponentChunk::SpawnInfo::SpawnInfo()
: m_runtimeEntityId(AZ::EntityId::InvalidEntityId)
, m_owningContextId(UnspecifiedNetBindingContextSequence)
, m_staticEntityId(AZ::EntityId::InvalidEntityId)
, m_sliceInstanceId(UnspecifiedSliceInstanceId)
, m_sliceAssetId(UnspecifiedSliceInstanceId, 0)
{
}
bool NetBindingComponentChunk::SpawnInfo::operator==(const SpawnInfo& rhs)
{
return m_owningContextId == rhs.m_owningContextId
&& m_runtimeEntityId == rhs.m_runtimeEntityId
&& m_staticEntityId == rhs.m_staticEntityId
&& m_serializedState == rhs.m_serializedState
&& m_sliceAssetId == rhs.m_sliceAssetId;
}
bool NetBindingComponentChunk::SpawnInfo::ContainsSerializedState() const
{
return !m_serializedState.empty();
}
void NetBindingComponentChunk::SpawnInfo::Marshaler::Marshal(GridMate::WriteBuffer& wb, const SpawnInfo& data)
{
wb.Write(data.m_owningContextId, GridMate::VlqU32Marshaler());
wb.Write(data.m_runtimeEntityId);
bool useSerializedState = data.ContainsSerializedState();
wb.Write(useSerializedState);
if (useSerializedState)
{
wb.Write(data.m_serializedState);
}
else
{
wb.Write(data.m_sliceAssetId);
wb.Write(data.m_staticEntityId);
wb.Write(data.m_sliceInstanceId);
}
}
void NetBindingComponentChunk::SpawnInfo::Marshaler::Unmarshal(SpawnInfo& data, GridMate::ReadBuffer& rb)
{
rb.Read(data.m_owningContextId, GridMate::VlqU32Marshaler());
rb.Read(data.m_runtimeEntityId);
bool hasSerializedState = false;
rb.Read(hasSerializedState);
if (hasSerializedState)
{
rb.Read(data.m_serializedState);
}
else
{
rb.Read(data.m_sliceAssetId);
rb.Read(data.m_staticEntityId);
rb.Read(data.m_sliceInstanceId);
}
}
NetBindingComponentChunk::NetBindingComponentChunk()
: m_bindingComponent(nullptr)
, m_spawnInfo("SpawnInfo")
, m_bindMap("ComponentBindMap")
{
m_spawnInfo.SetMaxIdleTime(0.f);
m_bindMap.SetMaxIdleTime(0.f);
}
void NetBindingComponentChunk::OnReplicaActivate(const GridMate::ReplicaContext& rc)
{
(void)rc;
if (IsMaster())
{
// Get and store entity spawn data
AZ_Assert(m_bindingComponent, "Entity binding is invalid!");
m_spawnInfo.Modify([&](SpawnInfo& spawnInfo)
{
spawnInfo.m_runtimeEntityId = static_cast<AZ::u64>(m_bindingComponent->GetEntity()->GetId());
bool isProceduralEntity = true;
AZ::SliceComponent::SliceInstanceAddress sliceInfo;
EntityContextId contextId = EntityContextId::CreateNull();
const AZ::EntityId bindingComponentEntityId = m_bindingComponent->GetEntityId();
EntityIdContextQueryBus::EventResult(contextId, bindingComponentEntityId,
&EntityIdContextQueryBus::Events::GetOwningContextId);
if (!contextId.IsNull())
{
EBUS_EVENT_RESULT(spawnInfo.m_owningContextId, NetBindingSystemBus, GetCurrentContextSequence);
SliceEntityRequestBus::EventResult(sliceInfo, bindingComponentEntityId,
&SliceEntityRequestBus::Events::GetOwningSlice);
bool isDynamicSliceEntity = sliceInfo.IsValid();
isProceduralEntity = !m_bindingComponent->IsLevelSliceEntity() && !isDynamicSliceEntity;
}
if (isProceduralEntity)
{
// write cloning info
AZ::SerializeContext* sc = nullptr;
EBUS_EVENT_RESULT(sc, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(sc, "Can't find SerializeContext!");
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8>> spawnDataStream(&spawnInfo.m_serializedState);
AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&spawnDataStream, *sc, AZ::DataStream::ST_BINARY);
objStream->WriteClass(m_bindingComponent->GetEntity());
objStream->Finalize();
}
else
{
// write slice info
if (sliceInfo.IsValid())
{
AZ::Data::AssetId sliceAssetId = sliceInfo.GetReference()->GetSliceAsset().GetId();
spawnInfo.m_sliceAssetId = AZStd::make_pair(sliceAssetId.m_guid, sliceAssetId.m_subId);
}
if (sliceInfo.GetInstance())
{
spawnInfo.m_sliceInstanceId = sliceInfo.GetInstance()->GetId();
}
AZ::EntityId staticEntityId;
EBUS_EVENT_RESULT(staticEntityId, NetBindingSystemBus, GetStaticIdFromEntityId, m_bindingComponent->GetEntity()->GetId());
spawnInfo.m_staticEntityId = static_cast<AZ::u64>(staticEntityId);
}
return true;
});
}
else
{
AZ::EntityId runtimeEntityId(m_spawnInfo.Get().m_runtimeEntityId);
NetBindingContextSequence owningContextId = m_spawnInfo.Get().m_owningContextId;
//TODO Move to Filter Hook
// Reject and cancel sessions with duplicate MachineIds?
// Reject and cancel sessions with duplicate entity ID creation requests?
//Check MachineId collision
bool collision = AZ::Entity::GetProcessSignature() == (m_spawnInfo.Get().m_runtimeEntityId & 0xFFFFFFFF);
AZ_Error("GridMate", !collision, "Replica received with duplicate Entity Machine IDs. Ignoring");
if (!collision)
{
//Check EntityID collision
AZ::Entity* entity = nullptr;
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, runtimeEntityId);
/*
* Only false if no machine ID collision and no entity ID collision
* And the entity is already active, it's possible the entity already exists in deactivated state as a cache mechanism
*/
collision = (entity != nullptr) && (entity->GetState() == AZ::Entity::State::Active);
}
/**
* Special case - static entities should not count as duplicates.
* Static entities are loaded with the level and will be bounded here.
*/
if (collision)
{
AZ::EntityId staticEntityId;
EBUS_EVENT_RESULT(staticEntityId, NetBindingSystemBus, GetStaticIdFromEntityId, runtimeEntityId);
if (staticEntityId == runtimeEntityId)
{
collision = false;
}
}
if (!collision) //Ignore duplicate runtime entity IDs
{
if (m_spawnInfo.Get().ContainsSerializedState())
{
// Spawn the entity from stream input data
AZ::IO::MemoryStream spawnData(m_spawnInfo.Get().m_serializedState.data(), m_spawnInfo.Get().m_serializedState.size());
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromStream, spawnData, runtimeEntityId, GetReplicaId(), owningContextId);
}
else
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = owningContextId;
spawnContext.m_sliceAssetId = AZ::Data::AssetId(m_spawnInfo.Get().m_sliceAssetId.first, m_spawnInfo.Get().m_sliceAssetId.second);
spawnContext.m_runtimeEntityId = runtimeEntityId;
spawnContext.m_staticEntityId = AZ::EntityId(m_spawnInfo.Get().m_staticEntityId);
spawnContext.m_sliceInstanceId = m_spawnInfo.Get().m_sliceInstanceId;
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, GetReplicaId(), spawnContext);
}
}
else //Fail early to prevent unnecessary spawning of duplicate entity IDs
{
//Misconfiguration or potential cheating/DoS?
AZ_Warning("NetBinding", false, "Received duplicate Entity ID %llu. Ignoring.", runtimeEntityId);
}
}
}
void NetBindingComponentChunk::OnReplicaDeactivate(const GridMate::ReplicaContext& rc)
{
(void)rc;
if (m_bindingComponent)
{
m_bindingComponent->UnbindFromNetwork();
}
}
bool NetBindingComponentChunk::AcceptChangeOwnership(GridMate::PeerId requestor, const GridMate::ReplicaContext& rc)
{
bool result = true;
if (m_bindingComponent)
{
EBUS_EVENT_ID_RESULT(result, m_bindingComponent->GetEntityId(), NetBindingEventsBus, OnEntityAcceptChangeOwnership, requestor, rc);
}
return result;
}
void NetBindingComponentChunk::OnReplicaChangeOwnership(const GridMate::ReplicaContext& rc)
{
if (m_bindingComponent)
{
EBUS_EVENT_ID(m_bindingComponent->GetEntityId(), NetBindingEventsBus, OnEntityChangeOwnership, rc);
}
}
} // namespace AzFramework
@@ -1,112 +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.
*
*/
#ifndef AZFRAMEWORK_NET_BINDING_COMPONENT_CHUNK_H
#define AZFRAMEWORK_NET_BINDING_COMPONENT_CHUNK_H
#include <AzCore/Component/ComponentBus.h>
#include <AzFramework/Network/NetBindingSystemBus.h>
#include <GridMate/Replica/ReplicaChunk.h>
#include <GridMate/Serialize/ContainerMarshal.h>
#include <GridMate/Serialize/DataMarshal.h>
#include <GridMate/Serialize/CompressionMarshal.h>
#include <AzFramework/Network/NetBindingSystemImpl.h>
namespace AzFramework
{
class NetBindingComponent;
class NetBindingComponentChunkDescriptor;
/**
* NetBindingComponentChunk is the counterpart of NetBindingComponent on the network side.
* It contains entity spawn data. It is created by NetBindingComponent during network
* binding on the master and initiates entity creation and binding on the proxy side.
*/
class NetBindingComponentChunk
: public GridMate::ReplicaChunk
{
friend NetBindingComponent;
friend NetBindingComponentChunkDescriptor;
public:
AZ_CLASS_ALLOCATOR(NetBindingComponentChunk, AZ::SystemAllocator, 0);
static const char* GetChunkName() { return "NetBindingComponentChunk"; }
NetBindingComponentChunk();
void SetBinding(NetBindingComponent* bindingComponent) { m_bindingComponent = bindingComponent; }
NetBindingComponent* GetBinding() const { return m_bindingComponent; }
protected:
///////////////////////////////////////////////////////////////////////
// ReplicaChunk
bool IsReplicaMigratable() override { return true; }
void OnReplicaActivate(const GridMate::ReplicaContext& rc) override;
void OnReplicaDeactivate(const GridMate::ReplicaContext& rc) override;
bool AcceptChangeOwnership(GridMate::PeerId requestor, const GridMate::ReplicaContext& rc) override;
void OnReplicaChangeOwnership(const GridMate::ReplicaContext& rc) override;
///////////////////////////////////////////////////////////////////////
NetBindingComponent* m_bindingComponent;
class SpawnInfo
{
public:
class Marshaler
{
public:
void Marshal(GridMate::WriteBuffer& wb, const SpawnInfo& data);
void Unmarshal(SpawnInfo& data, GridMate::ReadBuffer& rb);
};
class Throttle
{
public:
//! Always return true because SpawnInfo never changes
bool WithinThreshold(const SpawnInfo&) const { return true; }
void UpdateBaseline(const SpawnInfo& baseline) { (void)baseline; }
};
SpawnInfo();
bool operator==(const SpawnInfo& rhs);
bool ContainsSerializedState() const;
/**
* \brief Same as m_staticEntityId on authoritative entity with master replica
*/
AZ::u64 m_runtimeEntityId;
NetBindingContextSequence m_owningContextId;
AZStd::vector<AZ::u8> m_serializedState;
/**
* \brief EntityId of authoritative entity with master replica
*/
AZ::u64 m_staticEntityId;
AZStd::pair<AZ::Uuid, AZ::u32> m_sliceAssetId;
/**
* \brief uniquely identifies the slice instance that this entity is being replicated from
*/
AZ::SliceComponent::SliceInstanceId m_sliceInstanceId;
};
GridMate::DataSet<SpawnInfo, SpawnInfo::Marshaler, SpawnInfo::Throttle> m_spawnInfo;
GridMate::DataSet<AZStd::vector<AZ::ComponentId> > m_bindMap;
};
typedef AZStd::intrusive_ptr<NetBindingComponentChunk> NetBindingComponentChunkPtr;
} // namespace AZ
#endif // AZFRAMEWORK_NET_BINDING_COMPONENT_CHUNK_H
#pragma once
@@ -1,51 +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.
*
*/
#ifndef AZFRAMEWORK_NET_BINDING_EVENTS_BUS_H
#define AZFRAMEWORK_NET_BINDING_EVENTS_BUS_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/EntityId.h>
#include <GridMate/Replica/ReplicaCommon.h>
namespace AzFramework
{
/**
* NetBindingEventsBus
* Throws networking related entity events
*/
class NetBindingEvents
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef AZ::EntityId BusIdType;
virtual ~NetBindingEvents() {}
/**
* Called on authoritative(Master) entity when ownership of this entity is about to be transferred to another peer
* Returning false from this call will result in denying request for ownership transfer
*/
virtual bool OnEntityAcceptChangeOwnership(GridMate::PeerId requestor, const GridMate::ReplicaContext& rc) { (void)requestor; (void)rc; return true; }
/**
* Called when ownership transfer of an entity is finished.
*/
virtual void OnEntityChangeOwnership(const GridMate::ReplicaContext& rc) { (void)rc; }
};
typedef AZ::EBus<NetBindingEvents> NetBindingEventsBus;
} // namespace AzFramework
#endif // AZFRAMEWORK_NET_BINDING_EVENTS_BUS_H
#pragma once
@@ -1,112 +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.
*
*/
#ifndef AZFRAMEWORK_NET_BINDING_HANDLER_BUS_H
#define AZFRAMEWORK_NET_BINDING_HANDLER_BUS_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/std/parallel/mutex.h>
#include <GridMate/Replica/ReplicaCommon.h>
#include <AzCore/Slice/SliceComponent.h>
namespace AzFramework
{
/**
* The NetBindingSystemComponent notifies net binding handlers of binding events on this bus.
* The net binding component implements this interface and listens on the NetBindingHandlerBus.
*/
class NetBindingHandlerInterface
: public AZ::EBusTraits
{
public:
AZ_RTTI(NetBindingHandlerInterface, "{9F84E9FE-81A0-4105-9C51-6C42C83FECAF}");
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef AZ::EntityId BusIdType;
virtual ~NetBindingHandlerInterface() {}
/**
* Called to let the entity know that it should bind to the network.
* If bindTo is set, it means that the entity is a proxy and the handler
* should bind the entity to the specified
* replica, otherwise it should bind to a new replica and add it via
* NetBindingSystemBus::AddReplicaMaster.
*/
virtual void BindToNetwork(GridMate::ReplicaPtr bindTo) = 0;
/**
* Called to let the entity know that it should unbind from the network.
*/
virtual void UnbindFromNetwork() = 0;
/**
* Returns true if the entity is bound to the network.
*/
virtual bool IsEntityBoundToNetwork() = 0;
/**
* Returns true if the entity is authoritative on the local node.
*/
virtual bool IsEntityAuthoritative() = 0;
/**
* Flags the entity as part of the level slice.
*/
virtual void MarkAsLevelSliceEntity() = 0;
/**
* Set the slice instance id that this entity was spawned by and belongs to.
*/
virtual void SetSliceInstanceId(const AZ::SliceComponent::SliceInstanceId& sliceInstanceId) = 0;
/**
* Sets the Replica Priority
*/
virtual void SetReplicaPriority(GridMate::ReplicaPriority replicaPriority) = 0;
/**
* Request entity ownership to a given peer (by default to local peer)
*/
virtual void RequestEntityChangeOwnership(GridMate::PeerId peerId = GridMate::InvalidReplicaPeerId) = 0;
/**
* Gets the Replica Priority
*/
virtual GridMate::ReplicaPriority GetReplicaPriority() const = 0;
};
typedef AZ::EBus<NetBindingHandlerInterface> NetBindingHandlerBus;
/**
* Set of queries that might want to be made about the networking system
* mainly wraps up EBus calls to keep the implementing code a bit more readable
*/
class NetQuery
{
public:
AZ_RTTI(NetQuery, "{AA4C5699-889D-4A73-9AD2-53EB03D8BB99}");
virtual ~NetQuery() = default;
static AZ_FORCE_INLINE bool IsEntityAuthoritative(AZ::EntityId entityId)
{
bool result = true;
EBUS_EVENT_ID_RESULT(result,entityId,NetBindingHandlerBus,IsEntityAuthoritative);
return result;
}
};
} // namespace AzFramework
#endif // AZFRAMEWORK_NET_BINDING_HANDLER_BUS_H
#pragma once
@@ -1,119 +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
#ifndef AZFRAMEWORK_NET_BINDING_SYSTEM_BUS_H
#define AZFRAMEWORK_NET_BINDING_SYSTEM_BUS_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/Asset/AssetCommon.h>
#include <GridMate/Replica/ReplicaCommon.h>
#include <GridMate/Session/Session.h>
#include <AzCore/Slice/SliceComponent.h>
namespace AZ
{
namespace IO
{
class GenericStream;
}
}
namespace AzFramework
{
const AZ::SliceComponent::SliceInstanceId UnspecifiedSliceInstanceId = AZ::Uuid::CreateNull();
/**
*/
typedef AZ::u32 NetBindingContextSequence;
const NetBindingContextSequence UnspecifiedNetBindingContextSequence = 0;
/**
*/
struct NetBindingSliceContext
{
NetBindingContextSequence m_contextSequence;
AZ::Data::AssetId m_sliceAssetId;
AZ::EntityId m_staticEntityId;
AZ::EntityId m_runtimeEntityId;
/**
* \brief uniquely identifies the slice instance that this entity is being replicated from
*/
AZ::SliceComponent::SliceInstanceId m_sliceInstanceId;
};
/**
* The net binding system implements this interface and listens on the NetBindingSystemBus.
*
* Network binding is activated when OnNetworkSessionActivated event is received with the binding session,
* and is deactivated by the OnNetworkSessionDeactivated event.
*/
class NetBindingSystemInterface
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~NetBindingSystemInterface() {}
//! Returns true if a network session is available and entities should bind themselves to the network.
virtual bool ShouldBindToNetwork() = 0;
//! Returns the current entity context sequence
virtual NetBindingContextSequence GetCurrentContextSequence() = 0;
//! Get a level entity's static id.
virtual AZ::EntityId GetStaticIdFromEntityId(AZ::EntityId entity) = 0;
//! Get a level entity's id based on the static id
virtual AZ::EntityId GetEntityIdFromStaticId(AZ::EntityId staticEntityId) = 0;
//! Adds a bound replica to the network session as master.
virtual void AddReplicaMaster(AZ::Entity* entity, GridMate::ReplicaPtr replica) = 0;
//! Spawn and bind an entity from a slice
virtual void SpawnEntityFromSlice(GridMate::ReplicaId bindTo, const NetBindingSliceContext& bindToContext) = 0;
//! Spawn and bind an entity from stream
virtual void SpawnEntityFromStream(AZ::IO::GenericStream& spawnData, AZ::EntityId useEntityId, GridMate::ReplicaId bindTo, NetBindingContextSequence addToContext) = 0;
//! De-spawn an entity: deactivates or removes the entity.
/**
* /note @sliceInstanceId is the slice instance that the entity belongs to. If it's a level entity, then this should be AZ::Uuid::CreateNull()
*/
virtual void UnbindGameEntity(AZ::EntityId entity, const AZ::SliceComponent::SliceInstanceId& sliceInstanceId) = 0;
};
typedef AZ::EBus<NetBindingSystemInterface> NetBindingSystemBus;
class NetBindingSystemEvents
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//! Notification that a network session is created
virtual void OnNetworkSessionCreated(GridMate::GridSession* session) { (void)session; }
//! Notification that a network session is ready
virtual void OnNetworkSessionActivated(GridMate::GridSession* session) { (void)session; }
//! Notification that a network session is no longer available
virtual void OnNetworkSessionDeactivated(GridMate::GridSession* session) { (void)session; }
};
typedef AZ::EBus<NetBindingSystemEvents> NetBindingSystemEventsBus;
} // namespace AzFramework
#endif // AZFRAMEWORK_NET_BINDING_SYSTEM_BUS_H
@@ -1,66 +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.
*
*/
#include <AzCore/Serialization/EditContext.h>
#include <AzFramework/Network/NetBindingSystemComponent.h>
namespace AzFramework
{
NetBindingSystemComponent::NetBindingSystemComponent()
{
}
NetBindingSystemComponent::~NetBindingSystemComponent()
{
}
void NetBindingSystemComponent::Activate()
{
NetBindingSystemImpl::Init();
}
void NetBindingSystemComponent::Deactivate()
{
NetBindingSystemImpl::Shutdown();
}
void NetBindingSystemComponent::Reflect(AZ::ReflectContext* context)
{
NetBindingSystemImpl::Reflect(context);
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<NetBindingSystemComponent, AZ::Component>()
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<NetBindingSystemComponent>(
"NetBinding System", "Performs network binding for game entities.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Engine")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
;
}
}
}
void NetBindingSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("NetBindingSystemService", 0xa0ad6656));
}
void NetBindingSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("NetBindingSystemService", 0xa0ad6656));
}
} // namespace AzFramework
@@ -1,53 +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.
*
*/
#ifndef AZFRAMEWORK_NET_BINDING_SYSTEM_COMPONENT_H
#define AZFRAMEWORK_NET_BINDING_SYSTEM_COMPONENT_H
#include <AzFramework/Network/NetBindingSystemImpl.h>
#include <AzCore/Component/Component.h>
namespace AZ
{
class ReflectContext;
}
namespace AzFramework
{
/**
* NetBindingSystemComponent exposes NetBindingSystemImpl as a component
*/
class NetBindingSystemComponent
: public AZ::Component
, public NetBindingSystemImpl
{
friend class NetBindingSystemContextData;
public:
AZ_COMPONENT(NetBindingSystemComponent, "{B96548CC-0866-4BB3-A87B-BF0C4F69E8AC}");
NetBindingSystemComponent();
~NetBindingSystemComponent() override;
//////////////////////////////////////////////////////////////////////////
// Component overrides
void Activate() override;
void Deactivate() override;
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
//////////////////////////////////////////////////////////////////////////
};
} // namespace AzFramework
#endif // AZFRAMEWORK_NET_BINDING_SYSTEM_COMPONENT_H
#pragma once
@@ -1,957 +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.
*
*/
#include <AzFramework/Network/NetBindingSystemImpl.h>
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzFramework/Entity/SliceGameEntityOwnershipServiceBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Slice/SliceAsset.h>
#include <GridMate/Replica/Replica.h>
#include <GridMate/Replica/ReplicaChunk.h>
#include <GridMate/Replica/ReplicaChunkDescriptor.h>
#include <GridMate/Replica/ReplicaFunctions.h>
//#define Extra_Tracing
#undef Extra_Tracing
#if defined(Extra_Tracing)
#include <AzCore/Debug/Timer.h>
#define AZ_ExtraTracePrintf(window, ...) AZ::Debug::Trace::Instance().Printf(window, __VA_ARGS__);
#else
#define AZ_ExtraTracePrintf(window, ...)
#endif
namespace AzFramework
{
const AZStd::chrono::milliseconds NetBindingSystemImpl::s_sliceBindingTimeout = AZStd::chrono::milliseconds(5000);
namespace
{
NetBindingHandlerInterface* GetNetBindingHandler(AZ::Entity* entity)
{
NetBindingHandlerInterface* handler = nullptr;
for (AZ::Component* component : entity->GetComponents())
{
handler = azrtti_cast<NetBindingHandlerInterface*>(component);
if (handler)
{
break;
}
}
return handler;
}
}
NetBindingSliceInstantiationHandler::~NetBindingSliceInstantiationHandler()
{
// m_bindRequests in NetBindingSystemImpl could be cleaned before slice instantiation finished
if (m_state == State::Spawning)
{
AzFramework::SliceInstantiationResultBus::Handler::BusDisconnect();
SliceGameEntityOwnershipServiceRequestBus::Broadcast(
&SliceGameEntityOwnershipServiceRequests::CancelDynamicSliceInstantiation, m_ticket
);
}
for (AZ::Entity* entity : m_boundEntities)
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "Cleanup - deleting %llu\n", entity->GetId());
EBUS_EVENT(GameEntityContextRequestBus, DestroyGameEntity, entity->GetId());
}
}
void NetBindingSliceInstantiationHandler::InstantiateEntities()
{
if (m_sliceAssetId.IsValid())
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "InstantiateEntities sliceid %s\n",
m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str());
if (AZ::Data::AssetManager::IsReady())
{
auto remapFunc = [bindingQueue=m_bindingQueue](AZ::EntityId originalId, bool /*isEntityId*/, const AZStd::function<AZ::EntityId()>&) -> AZ::EntityId
{
auto iter = bindingQueue.find(originalId);
if (iter != bindingQueue.end())
{
return iter->second.m_desiredRuntimeEntityId;
}
return AZ::Entity::MakeId();
};
AZ::Data::Asset<AZ::Data::AssetData> asset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<AZ::DynamicSliceAsset>(m_sliceAssetId, AZ::Data::AssetLoadBehavior::Default);
SliceGameEntityOwnershipServiceRequestBus::BroadcastResult(m_ticket,
&SliceGameEntityOwnershipServiceRequests::InstantiateDynamicSlice, asset, AZ::Transform::Identity(), remapFunc);
SliceInstantiationResultBus::Handler::BusConnect(m_ticket);
m_state = State::Spawning;
}
else
{
AZ_Warning("NetBindingSystemImpl", false, "AssetManager was not ready when attempting to instantiate sliceid %s\n",
m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str());
InstantiationFailureCleanup();
}
}
}
bool NetBindingSliceInstantiationHandler::IsInstantiated() const
{
return m_state == State::Spawned;
}
bool NetBindingSliceInstantiationHandler::IsANewSliceRequest() const
{
return m_state == State::NewRequest && m_sliceAssetId.IsValid() && !m_ticket.IsValid();
}
bool NetBindingSliceInstantiationHandler::IsBindingComplete() const
{
return !SliceInstantiationResultBus::Handler::BusIsConnected() && m_bindingQueue.empty();
}
bool NetBindingSliceInstantiationHandler::HasActiveEntities() const
{
for (const AZ::Entity* entity : m_boundEntities)
{
if (entity->GetState() == AZ::Entity::State::Active)
{
return true;
}
}
return false;
}
void NetBindingSliceInstantiationHandler::OnSlicePreInstantiate(const AZ::Data::AssetId& /*sliceAssetId*/, const AZ::SliceComponent::SliceInstanceAddress& sliceAddress)
{
const auto& entityMapping = sliceAddress.GetInstance()->GetEntityIdToBaseMap();
const AZ::SliceComponent::EntityList& sliceEntities = sliceAddress.GetInstance()->GetInstantiated()->m_entities;
for (AZ::Entity *sliceEntity : sliceEntities)
{
auto it = entityMapping.find(sliceEntity->GetId());
AZ_Assert(it != entityMapping.end(), "Failed to retrieve static entity id for a slice entity!");
const AZ::EntityId staticEntityId = it->second;
auto itBindRecord = m_bindingQueue.find(staticEntityId);
if (itBindRecord != m_bindingQueue.end())
{
AZ_Assert(GetNetBindingHandler(sliceEntity), "Slice entity matched the static id of replicated entity, but there is no valid NetBindingHandlerInterface on it!");
itBindRecord->second.m_actualRuntimeEntityId = sliceEntity->GetId();
}
else if (GetNetBindingHandler(sliceEntity))
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "OnSlicePreInstantiate late bindRequest, slice %s, staticid %llu, spawned %llu\n",
m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str(),
static_cast<AZ::u64>(staticEntityId),
static_cast<AZ::u64>(sliceEntity->GetId()));
BindRequest& request = m_bindingQueue[staticEntityId];
request.m_desiredRuntimeEntityId = staticEntityId;
request.m_actualRuntimeEntityId = sliceEntity->GetId();
request.m_requestTime = m_bindTime;
request.m_state = BindRequest::State::PlaceholderBind;
}
sliceEntity->SetRuntimeActiveByDefault(false);
}
}
void NetBindingSliceInstantiationHandler::OnSliceInstantiated(const AZ::Data::AssetId& /*sliceAssetId*/, const AZ::SliceComponent::SliceInstanceAddress& sliceAddress)
{
SliceInstantiationResultBus::Handler::BusDisconnect();
CloseEntityMap(sliceAddress.GetInstance()->GetEntityIdMap());
const AZ::SliceComponent::EntityList sliceEntities = sliceAddress.GetInstance()->GetInstantiated()->m_entities;
for (AZ::Entity *sliceEntity : sliceEntities)
{
auto it = sliceAddress.GetInstance()->GetEntityIdToBaseMap().find(sliceEntity->GetId());
AZ_Assert(it != sliceAddress.GetInstance()->GetEntityIdToBaseMap().end(), "Failed to retrieve static entity id for a slice entity!");
const AZ::EntityId staticEntityId = it->second;
const auto itUnbound = m_bindingQueue.find(staticEntityId);
if (itUnbound == m_bindingQueue.end())
{
/*
* Remove entities that aren't meant to be net bounded.
*/
if (!GetNetBindingHandler(sliceEntity))
{
EBUS_EVENT(GameEntityContextRequestBus, DestroyGameEntity, sliceEntity->GetId());
continue;
}
}
AZ_ExtraTracePrintf("NetBindingSystemImpl", "Adding %llu \n", sliceEntity->GetId());
m_boundEntities.push_back(sliceEntity);
}
m_state = State::Spawned;
}
void NetBindingSliceInstantiationHandler::OnSliceInstantiationFailed(const AZ::Data::AssetId& sliceAssetId)
{
SliceInstantiationResultBus::Handler::BusDisconnect();
AZ_UNUSED(sliceAssetId);
AZ_TracePrintf("NetBindingSystemImpl", "Failed to instantiate a slice %s!", sliceAssetId.ToString<AZStd::string>().c_str());
InstantiationFailureCleanup();
}
void NetBindingSliceInstantiationHandler::InstantiationFailureCleanup()
{
m_boundEntities.clear();
m_bindingQueue.clear();
// With m_bindingQueue empty, this slice instance handler will be removed on the next tick of NetBindingSystemImpl
m_state = State::Failed;
}
void NetBindingSliceInstantiationHandler::UseCacheFor(BindRequest& request, const AZ::EntityId& staticEntityId)
{
AZ_Warning("NetBindingSystemImpl", !m_staticToRuntimeEntityMap.empty(), "An empty slice, really? static %llu",
static_cast<AZ::u64>(staticEntityId));
const auto actualRuntimeIter = m_staticToRuntimeEntityMap.find(staticEntityId);
if (actualRuntimeIter == m_staticToRuntimeEntityMap.end())
{
AZ_Warning("NetBindingSystemImpl", false, "Wrong mapping, expected cache to have entity %llu for slice %s \n",
static_cast<AZ::u64>(staticEntityId),
m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str());
#if defined(Extra_Tracing)
for (auto& item: m_staticToRuntimeEntityMap)
{
AZ_UNUSED(item);
AZ_ExtraTracePrintf("NetBindingSystemImpl", "mapping had %llu to %llu \n",
static_cast<AZ::u64>(item.first),
static_cast<AZ::u64>(item.second));
}
#endif
return;
}
const AZ::EntityId actualRuntimeEntityId = actualRuntimeIter->second;
const auto itCache = AZStd::find_if(m_boundEntities.begin(), m_boundEntities.end(), [&actualRuntimeEntityId](AZ::Entity* entity) {
return entity->GetId() == actualRuntimeEntityId;
});
if (itCache != m_boundEntities.end())
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "OnSlicePreInstantiate late bindRequest, slice %s, staticid %llu, spawned %llu\n",
m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str(),
static_cast<AZ::u64>(staticEntityId),
static_cast<AZ::u64>(actualRuntimeEntityId));
request.m_actualRuntimeEntityId = actualRuntimeEntityId;
request.m_desiredRuntimeEntityId = staticEntityId;
}
else
{
AZ_Warning("NetBindingSystemImpl", false, "Expected cache to have entity %llu for slice %s \n",
static_cast<AZ::u64>(request.m_desiredRuntimeEntityId),
m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str());
}
}
void NetBindingSliceInstantiationHandler::CloseEntityMap(
const AZ::SliceComponent::EntityIdToEntityIdMap& staticToRuntimeMap)
{
m_staticToRuntimeEntityMap.clear();
for (auto& item : staticToRuntimeMap)
{
m_staticToRuntimeEntityMap[item.first] = item.second;
}
}
NetBindingSystemContextData::NetBindingSystemContextData()
: m_bindingContextSequence("BindingContextSequence", UnspecifiedNetBindingContextSequence)
{
}
void NetBindingSystemContextData::OnReplicaActivate(const GridMate::ReplicaContext& rc)
{
(void)rc;
NetBindingSystemImpl* system = static_cast<NetBindingSystemImpl*>(NetBindingSystemBus::FindFirstHandler());
AZ_Assert(system, "NetBindingSystemContextData requires a valid NetBindingSystemComponent to function!");
system->OnContextDataActivated(this);
}
void NetBindingSystemContextData::OnReplicaDeactivate(const GridMate::ReplicaContext& rc)
{
(void)rc;
NetBindingSystemImpl* system = static_cast<NetBindingSystemImpl*>(NetBindingSystemBus::FindFirstHandler());
if (system)
{
system->OnContextDataDeactivated(this);
}
}
NetBindingSystemImpl::NetBindingSystemImpl()
: m_bindingSession(nullptr)
, m_currentBindingContextSequence(UnspecifiedNetBindingContextSequence)
, m_isAuthoritativeRootSliceLoad(false)
, m_overrideRootSliceLoadAuthoritative(false)
{
}
NetBindingSystemImpl::~NetBindingSystemImpl()
{
}
void NetBindingSystemImpl::Init()
{
NetBindingSystemBus::Handler::BusConnect();
NetBindingSystemEventsBus::Handler::BusConnect();
// Start listening for game context events
EntityContextId gameContextId = EntityContextId::CreateNull();
EBUS_EVENT_RESULT(gameContextId, GameEntityContextRequestBus, GetGameEntityContextId);
if (!gameContextId.IsNull())
{
EntityContextEventBus::Handler::BusConnect(gameContextId);
}
}
void NetBindingSystemImpl::Shutdown()
{
EntityContextEventBus::Handler::BusDisconnect();
NetBindingSystemEventsBus::Handler::BusDisconnect();
NetBindingSystemBus::Handler::BusDisconnect();
m_contextData.reset();
}
bool NetBindingSystemImpl::ShouldBindToNetwork()
{
return m_contextData && m_contextData->ShouldBindToNetwork();
}
NetBindingContextSequence NetBindingSystemImpl::GetCurrentContextSequence()
{
return m_currentBindingContextSequence;
}
bool NetBindingSystemImpl::ReadyToAddReplica() const
{
return m_bindingSession && m_bindingSession->GetReplicaMgr();
}
void NetBindingSystemImpl::AddReplicaMaster(AZ::Entity* entity, GridMate::ReplicaPtr replica)
{
bool addReplica = ShouldBindToNetwork();
AZ_Assert(addReplica, "Entities shouldn't be binding to the network right now!");
if (addReplica)
{
if (ReadyToAddReplica())
{
m_bindingSession->GetReplicaMgr()->AddMaster(replica);
}
else
{
m_addMasterRequests.push_back(AZStd::make_pair(entity->GetId(), replica));
}
}
}
AZ::EntityId NetBindingSystemImpl::GetStaticIdFromEntityId(AZ::EntityId entityId)
{
AZ::EntityId staticId = entityId; // if no static id mapping is found, then the static id is the same as the runtime id
// If entity came from a slice, try to get the mapping from it
AZ::SliceComponent::SliceInstanceAddress sliceInfo;
SliceEntityRequestBus::EventResult(sliceInfo, entityId, &SliceEntityRequestBus::Events::GetOwningSlice);
AZ::SliceComponent::SliceInstance* sliceInstance = sliceInfo.GetInstance();
if (sliceInstance)
{
const auto it = sliceInstance->GetEntityIdToBaseMap().find(entityId);
if (it != sliceInstance->GetEntityIdToBaseMap().end())
{
staticId = it->second;
}
}
return staticId;
}
AZ::EntityId NetBindingSystemImpl::GetEntityIdFromStaticId(AZ::EntityId staticEntityId)
{
AZ::EntityId runtimeId = AZ::EntityId();
// if we can find an entity with the static id, then the static id is the same as the runtime id.
AZ::Entity* entity = nullptr;
EBUS_EVENT(AZ::ComponentApplicationBus, FindEntity, staticEntityId);
if (entity)
{
runtimeId = staticEntityId;
}
return runtimeId;
}
void NetBindingSystemImpl::SpawnEntityFromSlice(GridMate::ReplicaId bindTo, const NetBindingSliceContext& bindToContext)
{
auto& sliceQueue = m_bindRequests[bindToContext.m_contextSequence];
const bool slicePresent = sliceQueue.find(bindToContext.m_sliceInstanceId) != sliceQueue.end();
auto iterSliceRequest = sliceQueue.insert_key(bindToContext.m_sliceInstanceId);
NetBindingSliceInstantiationHandler& sliceHandler = iterSliceRequest.first->second;
sliceHandler.m_sliceAssetId = bindToContext.m_sliceAssetId;
sliceHandler.m_sliceInstanceId = bindToContext.m_sliceInstanceId;
BindRequest& request = sliceHandler.m_bindingQueue[bindToContext.m_staticEntityId];
if (!slicePresent)
{
request.m_state = BindRequest::State::FirstBindInSlice;
}
else
{
request.m_state = BindRequest::State::LateBind;
}
AZ_ExtraTracePrintf("NetBindingSystemImpl", "SpawnEntityFromSlice late, slice %s, static %llu, desired %llu, state %d \n",
bindToContext.m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str(),
static_cast<AZ::u64>(bindToContext.m_staticEntityId),
static_cast<AZ::u64>(bindToContext.m_runtimeEntityId),
request.m_state);
sliceHandler.m_bindTime = Now();
request.m_bindTo = bindTo;
request.m_desiredRuntimeEntityId = bindToContext.m_runtimeEntityId;
request.m_requestTime = Now();
if (sliceHandler.IsInstantiated())
{
// The slice has been instantiated now, thus we have to use the cache to populated the request with the entity.
sliceHandler.UseCacheFor(request, bindToContext.m_staticEntityId);
}
}
void NetBindingSystemImpl::SpawnEntityFromStream(AZ::IO::GenericStream& spawnData, AZ::EntityId useEntityId, GridMate::ReplicaId bindTo, NetBindingContextSequence addToContext)
{
auto& requestQueue = m_spawnRequests[addToContext];
requestQueue.push_back();
SpawnRequest& request = requestQueue.back();
request.m_bindTo = bindTo;
request.m_useEntityId = useEntityId;
request.m_spawnDataBuffer.resize_no_construct(spawnData.GetLength());
spawnData.Read(request.m_spawnDataBuffer.size(), request.m_spawnDataBuffer.data());
}
void NetBindingSystemImpl::OnNetworkSessionActivated(GridMate::GridSession* session)
{
AZ_Assert(!m_bindingSession, "We already have an active session! Was the previous session deactivated?");
if (!m_bindingSession)
{
m_bindingSession = session;
if (m_bindingSession->IsHost())
{
GridMate::Replica* replica = CreateSystemReplica();
session->GetReplicaMgr()->AddMaster(replica);
}
}
}
void NetBindingSystemImpl::OnNetworkSessionDeactivated(GridMate::GridSession* session)
{
if (session == m_bindingSession)
{
m_bindingSession = nullptr;
}
}
void NetBindingSystemImpl::UnbindGameEntity(AZ::EntityId entityId, const AZ::SliceComponent::SliceInstanceId& sliceInstanceId)
{
if (!m_bindRequests.empty())
{
const auto itCurrentContextQueue = m_bindRequests.lower_bound(GetCurrentContextSequence());
if (itCurrentContextQueue != m_bindRequests.end())
{
if (itCurrentContextQueue->first == GetCurrentContextSequence())
{
const auto itSliceHandler = itCurrentContextQueue->second.find(sliceInstanceId);
if (itSliceHandler != itCurrentContextQueue->second.end())
{
NetBindingSliceInstantiationHandler& sliceHandler = itSliceHandler->second;
for (AZ::Entity* entity : sliceHandler.m_boundEntities)
{
if (entity->GetId() == entityId)
{
entity->Deactivate();
return;
}
}
// clean any relevant bind requests as well
const auto bindQueueItem = sliceHandler.m_bindingQueue.find(entityId);
if (bindQueueItem != sliceHandler.m_bindingQueue.end())
{
sliceHandler.m_bindingQueue.erase(bindQueueItem);
return;
}
}
}
}
}
AZ_ExtraTracePrintf("NetBindingSystemImpl", "Not in cache - deleting %llu \n", entityId);
EBUS_EVENT(GameEntityContextRequestBus, DestroyGameEntity, entityId);
}
void NetBindingSystemImpl::OnEntityContextReset()
{
const bool isContextOwner = m_contextData && m_contextData->IsMaster() && m_bindingSession && m_bindingSession->IsHost();
if (isContextOwner)
{
++m_currentBindingContextSequence;
NetBindingSystemContextData* context = static_cast<NetBindingSystemContextData*>(m_contextData.get());
context->m_bindingContextSequence.Set(m_currentBindingContextSequence);
}
}
bool NetBindingSystemImpl::IsAuthoritateLoad() const
{
if (m_overrideRootSliceLoadAuthoritative)
{
return m_isAuthoritativeRootSliceLoad;
}
return !m_bindingSession || m_bindingSession->IsHost();
}
void NetBindingSystemImpl::UpdateClock(float deltaTime)
{
m_currentTime += AZStd::chrono::milliseconds(aznumeric_cast<int>(deltaTime * AZStd::milli::den));
}
AZStd::chrono::system_clock::time_point NetBindingSystemImpl::Now() const
{
return m_currentTime;
}
void NetBindingSystemImpl::OnEntityContextLoadedFromStream(const AZ::SliceComponent::EntityList& contextEntities)
{
const bool isAuthoritativeLoad = IsAuthoritateLoad();
for (AZ::Entity* entity : contextEntities)
{
NetBindingHandlerInterface* netBinder = GetNetBindingHandler(entity);
if (netBinder)
{
netBinder->MarkAsLevelSliceEntity();
}
if (!isAuthoritativeLoad && netBinder)
{
entity->SetRuntimeActiveByDefault(false);
auto& slicesQueue = m_bindRequests[GetCurrentContextSequence()];
auto& sliceHandler = slicesQueue[UnspecifiedSliceInstanceId];
BindRequest& request = sliceHandler.m_bindingQueue[entity->GetId()];
request.m_actualRuntimeEntityId = entity->GetId();
request.m_requestTime = Now();
}
}
}
void NetBindingSystemImpl::OnTick(float deltaTime, AZ::ScriptTimePoint time)
{
AZ_UNUSED(time);
UpdateClock(deltaTime);
UpdateContextSequence();
#if defined(Extra_Tracing)
static AZ::Debug::Timer sTimer;
sTimer.Stamp();
#endif
ProcessBindRequests();
#if defined(Extra_Tracing)
const float seconds = sTimer.StampAndGetDeltaTimeInSeconds();
static float debugPeriod = 2.f;
static float accumulator = 0;
static float totalTimeTaken = 0;
static AZ::u32 totalTicks = 0;
accumulator += deltaTime;
totalTimeTaken += seconds;
totalTicks++;
if (accumulator >= debugPeriod)
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "ProcessBindRequests() took %f sec \n", totalTicks > 0 ? totalTimeTaken / totalTicks : 0);
accumulator -= debugPeriod;
totalTimeTaken = 0;
totalTicks = 0;
}
#endif
ProcessSpawnRequests();
}
int NetBindingSystemImpl::GetTickOrder()
{
return AZ::TICK_PLACEMENT + 1;
}
void NetBindingSystemImpl::UpdateContextSequence()
{
NetBindingSystemContextData* contextChunk = static_cast<NetBindingSystemContextData*>(m_contextData.get());
if (m_currentBindingContextSequence != contextChunk->m_bindingContextSequence.Get())
{
m_currentBindingContextSequence = contextChunk->m_bindingContextSequence.Get();
}
}
GridMate::Replica* NetBindingSystemImpl::CreateSystemReplica()
{
AZ_Assert(m_bindingSession->IsHost(), "CreateSystemReplica should only be called on the host!");
GridMate::Replica* replica = GridMate::Replica::CreateReplica("NetBindingSystem");
NetBindingSystemContextData* contextChunk = GridMate::CreateReplicaChunk<NetBindingSystemContextData>();
replica->AttachReplicaChunk(contextChunk);
return replica;
}
void NetBindingSystemImpl::OnContextDataActivated(GridMate::ReplicaChunkPtr contextData)
{
AZ_Assert(!m_contextData, "We already have our context!");
m_contextData = contextData;
// Make sure we always have the unspecified entry. This should also
// be the lower_bound in the map and assuming it is always there
// makes things simpler.
m_spawnRequests.insert(UnspecifiedNetBindingContextSequence);
m_bindRequests.insert(UnspecifiedNetBindingContextSequence);
if (contextData->IsMaster())
{
++m_currentBindingContextSequence;
static_cast<NetBindingSystemContextData*>(contextData.get())->m_bindingContextSequence.Set(m_currentBindingContextSequence);
}
else
{
UpdateContextSequence();
}
AZ::TickBus::Handler::BusConnect();
EBUS_EVENT(AzFramework::NetBindingHandlerBus, BindToNetwork, nullptr);
}
void NetBindingSystemImpl::OnContextDataDeactivated(GridMate::ReplicaChunkPtr contextData)
{
AZ_Assert(m_contextData == contextData, "This is not our context!");
m_contextData = nullptr;
AZ::TickBus::Handler::BusDisconnect();
m_spawnRequests.clear();
m_bindRequests.clear();
m_addMasterRequests.clear();
m_currentBindingContextSequence = UnspecifiedNetBindingContextSequence;
}
void NetBindingSystemImpl::ProcessSpawnRequests()
{
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "NetBindingSystemComponent requires a valid SerializeContext in order to spawn entities!");
const auto spawnFunc = [=](SpawnRequest& spawnData, AZ::EntityId useEntityId, bool addToContext)
{
AZ::Entity* proxyEntity = nullptr;
AZ::ObjectStream::ClassReadyCB readyCB([&](void* classPtr, const AZ::Uuid& classId, AZ::SerializeContext* sc)
{
(void)classId;
(void)sc;
proxyEntity = static_cast<AZ::Entity*>(classPtr);
});
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8> > stream(&spawnData.m_spawnDataBuffer);
AZ::ObjectStream::LoadBlocking(&stream, *serializeContext, readyCB);
AZ_Warning("NetBindingSystemImpl", proxyEntity, "Could not spawn entity from stream %llu", useEntityId);
if (proxyEntity)
{
proxyEntity->SetId(useEntityId);
if (!BindAndActivate(proxyEntity, spawnData.m_bindTo, addToContext, AZ::Uuid::CreateNull()))
{
AzFramework::EntityContextId contextId = AzFramework::EntityContextId::CreateNull();
AzFramework::EntityIdContextQueryBus::EventResult(
contextId, proxyEntity->GetId(), &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
if (contextId.IsNull())
{
delete proxyEntity;
}
else
{
GameEntityContextRequestBus::Broadcast(
&GameEntityContextRequestBus::Events::DestroyGameEntity, proxyEntity->GetId());
}
}
}
};
if (!m_spawnRequests.empty())
{
SpawnRequestContextContainerType::iterator itContextQueue = m_spawnRequests.lower_bound(UnspecifiedNetBindingContextSequence);
AZ_Assert(itContextQueue->first == UnspecifiedNetBindingContextSequence, "We should always have the unspecified (aka global entity) spawn queue!");//
// Process requests for global entities (not part of any context)
SpawnRequestContainerType& globalQueue = itContextQueue->second;
for (SpawnRequest& request : globalQueue)
{
spawnFunc(request, request.m_useEntityId, false);
}
globalQueue.clear();
if (GetCurrentContextSequence() != UnspecifiedNetBindingContextSequence)
{
++itContextQueue;
// Clear any obsolete requests (any contexts below the current context sequence)
SpawnRequestContextContainerType::iterator itCurrentContextQueue = m_spawnRequests.lower_bound(GetCurrentContextSequence());
if (itContextQueue != itCurrentContextQueue)
{
m_spawnRequests.erase(itContextQueue, itCurrentContextQueue);
}
// Spawn any entities for the current context
if (itCurrentContextQueue != m_spawnRequests.end())
{
if (itCurrentContextQueue->first == GetCurrentContextSequence())
{
for (SpawnRequest& request : itCurrentContextQueue->second)
{
spawnFunc(request, request.m_useEntityId, true);
}
itCurrentContextQueue->second.clear();
}
}
}
}
}
void NetBindingSystemImpl::ProcessBindRequests()
{
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "NetBindingSystemComponent requires a valid SerializeContext in order to spawn entities!");
if (!m_bindRequests.empty())
{
BindRequestContextContainerType::iterator itContextQueue = m_bindRequests.lower_bound(UnspecifiedNetBindingContextSequence);
AZ_Assert(itContextQueue->first == UnspecifiedNetBindingContextSequence, "We should always have the unspecified/global spawn queue!");
if (GetCurrentContextSequence() != UnspecifiedNetBindingContextSequence)
{
++itContextQueue;
// Clear any obsolete requests (any contexts below the current context sequence)
BindRequestContextContainerType::iterator itCurrentContextQueue = m_bindRequests.lower_bound(GetCurrentContextSequence());
if (itContextQueue != itCurrentContextQueue)
{
m_bindRequests.erase(itContextQueue, itCurrentContextQueue);
}
// Spawn any proxy entities for the current context
if (itCurrentContextQueue != m_bindRequests.end())
{
if (itCurrentContextQueue->first == GetCurrentContextSequence())
{
for (auto itSliceHandler = itCurrentContextQueue->second.begin(); itSliceHandler != itCurrentContextQueue->second.end(); /*++itSliceHandler*/)
{
NetBindingSliceInstantiationHandler& sliceHandler = itSliceHandler->second;
// If this is a new slice request, instantiate it
if (sliceHandler.IsANewSliceRequest())
{
sliceHandler.InstantiateEntities();
}
/*
* A slice instance is kept alive for caching purposes. As we check each bind request for its readiness,
* we are also going to check if the slice instance itself has become inactive and needs to be removed.
*/
bool mightBeInactiveSlice = true;
if (sliceHandler.m_bindingQueue.empty() && sliceHandler.HasActiveEntities())
{
// The slice instance is spawned and full bound.
mightBeInactiveSlice = false;
}
// If the entity is ready to be bound to the network, bind it.
// NOTE: It is possible for entities spawned from a slice containing multiple entities with net binding
// to never receive their replica counterpart, either because the replica was destroyed, or was interest
// filtered. We don't have a very good pipeline to prevent these slices from being authored, so if we
// encounter them, we will delete them after a timeout.
for (auto itRequest = sliceHandler.m_bindingQueue.begin(); itRequest != sliceHandler.m_bindingQueue.end(); /*++itRequest*/)
{
BindRequest& request = itRequest->second;
if (request.m_bindTo != GridMate::InvalidReplicaId && request.m_actualRuntimeEntityId.IsValid())
{
AZ::Entity* proxyEntity = nullptr;
EBUS_EVENT_RESULT(proxyEntity, AZ::ComponentApplicationBus, FindEntity, request.m_actualRuntimeEntityId);
AZ_Warning("NetBindingSystemImpl", proxyEntity, "Could not find entity for binding %llu", request.m_actualRuntimeEntityId);
if (proxyEntity)
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "BindAndActivate desired id %llu, actual %llu, slice %s \n",
static_cast<AZ::u64>(request.m_desiredRuntimeEntityId),
static_cast<AZ::u64>(request.m_actualRuntimeEntityId),
sliceHandler.m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str());
BindAndActivate(proxyEntity, request.m_bindTo, false, sliceHandler.m_sliceInstanceId);
}
itRequest = sliceHandler.m_bindingQueue.erase(itRequest);
// The slice instance is not fully bound. It may remain for a while for caching purposes.
mightBeInactiveSlice = false;
}
else if (AZStd::chrono::milliseconds(Now() - request.m_requestTime) > s_sliceBindingTimeout)
{
// If the real request never showed up, then no need for a trace
if (request.m_state == BindRequest::State::FirstBindInSlice ||
request.m_state == BindRequest::State::LateBind)
{
AZ_TracePrintf("NetBindingSystemImpl", "Entity with static id [%llu], slice [%s]\n is still unbound after %llu ms. Discarding unbound entity.\n",
static_cast<AZ::u64>(request.m_actualRuntimeEntityId),
sliceHandler.m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str(),
s_sliceBindingTimeout.count());
}
switch (sliceHandler.m_state)
{
case NetBindingSliceInstantiationHandler::State::NewRequest:
case NetBindingSliceInstantiationHandler::State::Spawning:
// The slice instance isn't ready yet. We will wait to consider the timing logic until it is ready.
mightBeInactiveSlice = false;
break;
case NetBindingSliceInstantiationHandler::State::Spawned:
case NetBindingSliceInstantiationHandler::State::Failed:
// Now the timing logic for removing the slice instance becomes valid.
mightBeInactiveSlice = true;
break;
default:
break;
}
++itRequest;
}
else
{
mightBeInactiveSlice = false;
++itRequest;
}
}
if (mightBeInactiveSlice && !sliceHandler.HasActiveEntities())
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "Removing inactive slice %s \n",
sliceHandler.m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str());
itSliceHandler = itCurrentContextQueue->second.erase(itSliceHandler);
}
else
{
++itSliceHandler;
}
}
}
}
}
}
// Spawn replicas for any local entities that are still valid
for (auto& addRequest : m_addMasterRequests)
{
AZ::Entity* entity = nullptr;
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, addRequest.first);
if (entity)
{
m_bindingSession->GetReplicaMgr()->AddMaster(addRequest.second);
}
}
m_addMasterRequests.clear();
}
bool NetBindingSystemImpl::BindAndActivate(AZ::Entity* entity, GridMate::ReplicaId replicaId, bool addToContext,
const AZ::SliceComponent::SliceInstanceId& sliceInstanceId)
{
bool success = false;
if ( ShouldBindToNetwork() )
{
const GridMate::ReplicaPtr bindTo = m_contextData->GetReplicaManager()->FindReplica(replicaId);
if (bindTo)
{
if (addToContext)
{
EBUS_EVENT(GameEntityContextRequestBus, AddGameEntity, entity);
}
if (entity->GetState() == AZ::Entity::State::Constructed)
{
entity->Init();
}
NetBindingHandlerInterface* binding = GetNetBindingHandler(entity);
AZ_Warning("NetBindingSystemImpl", binding, "Can't find NetBindingComponent on entity %llu (%s)!", static_cast<AZ::u64>(entity->GetId()), entity->GetName().c_str());
if (binding)
{
binding->BindToNetwork(bindTo);
binding->SetSliceInstanceId(sliceInstanceId);
entity->Activate();
success = true;
}
}
else
{
// NOTE: It is possible for entities spawned from a slice containing multiple entities with net binding
// to never receive their replica counterpart, either because the replica was destroyed, or was interest
// filtered.
AZ_ExtraTracePrintf("NetBindingSystemImpl", "Failed to bind entity %llu - could not find replica %u", entity->GetId(), replicaId);
}
}
return success;
}
void NetBindingSystemImpl::Reflect(AZ::ReflectContext* context)
{
if (context)
{
// We need to register the chunk type, and this would be a good time to do so.
if (!GridMate::ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(GridMate::ReplicaChunkClassId(NetBindingSystemContextData::GetChunkName())))
{
GridMate::ReplicaChunkDescriptorTable::Get().RegisterChunkType<AzFramework::NetBindingSystemContextData>();
}
}
}
} // namespace AzFramework
@@ -1,311 +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 <AzFramework/Network/NetBindingSystemBus.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzFramework/Slice/SliceInstantiationBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/containers/unordered_map.h>
#include <GridMate/Serialize/CompressionMarshal.h>
namespace AzFramework
{
/**
* \brief Represents a request to bind a particular replica to an entity
*/
class BindRequest
{
public:
BindRequest()
: m_bindTo(GridMate::InvalidReplicaId)
, m_state(State::None)
{
}
GridMate::ReplicaId m_bindTo;
AZ::EntityId m_desiredRuntimeEntityId;
AZ::EntityId m_actualRuntimeEntityId;
AZStd::chrono::system_clock::time_point m_requestTime;
/**
* \brief Represents the state of this bind request and it's relation to the slice instantiation process
*/
enum class State : AZ::u8
{
None,
/**
* \brief This is the first request that led to instantiating a slice
*/
FirstBindInSlice,
/**
* \brief The request is a placeholder in case a real bind request arrives later.
* Some part of the slice may never be bound (e.g. if a replica is omitted by Interest Manager)
*/
PlaceholderBind,
/**
* \brief The real request did arrive to replace a placeholder request.
*/
LateBind,
};
State m_state;
};
typedef AZStd::unordered_map<AZ::EntityId, BindRequest> BindRequestContainerType;
/**
* \brief Represents a slice instance being instantiated and bound to replicas
* \note It's possible that only some of the entities are activated and bound to replicas.
*/
class NetBindingSliceInstantiationHandler
: public SliceInstantiationResultBus::Handler
{
public:
~NetBindingSliceInstantiationHandler() override;
void InstantiateEntities();
bool IsInstantiated() const;
bool IsANewSliceRequest() const;
bool IsBindingComplete() const;
/**
* \note Returns false if there are no entities in the slice or the slice instance isn't ready yet.
* \return true if any of the entities from the slice are active
*/
bool HasActiveEntities() const;
//////////////////////////////////////////////////////////////////////////
// SliceInstantiationResultBus
void OnSlicePreInstantiate(const AZ::Data::AssetId& sliceAssetId, const AZ::SliceComponent::SliceInstanceAddress& sliceAddress) override;
void OnSliceInstantiated(const AZ::Data::AssetId& sliceAssetId, const AZ::SliceComponent::SliceInstanceAddress& sliceAddress) override;
void OnSliceInstantiationFailed(const AZ::Data::AssetId& sliceAssetId) override;
//////////////////////////////////////////////////////////////////////////
void InstantiationFailureCleanup();
void UseCacheFor(BindRequest& request, const AZ::EntityId& staticEntityId);
void CloseEntityMap(const AZ::SliceComponent::EntityIdToEntityIdMap& staticToRuntimeMap);
AZ::Data::AssetId m_sliceAssetId;
BindRequestContainerType m_bindingQueue;
SliceInstantiationTicket m_ticket;
/**
* \breif a cache of entities that might be networked at some point
* \note they might be bound and unbound if their replicas leave and come back in the view
*/
AZStd::vector<AZ::Entity*> m_boundEntities;
/**
* \brief identifies which slice instance the instantiation will be performed for
*/
AZ::SliceComponent::SliceInstanceId m_sliceInstanceId;
/**
* \brief when was the request to spawn a slice and bind it made
*/
AZStd::chrono::system_clock::time_point m_bindTime;
AZ::SliceComponent::EntityIdToEntityIdMap m_staticToRuntimeEntityMap;
/**
* \brief The state of the slice instance.
*/
enum class State
{
/**
* \brief Has not started instantiating the slice instance.
*/
NewRequest,
/**
* \brief Waiting on the slice to spawn.
*/
Spawning,
/**
* \brief Successfully spawned the slice assets.
*/
Spawned,
/**
* \brief Failed to spawn the slice.
*/
Failed
};
State m_state = State::NewRequest;
};
/**
* NetBindingSystemImpl works in conjunction with NetBindingComponent and
* NetBindingComponentChunk to perform network binding for game entities.
*
* It is responsible for adding entity replicas to the network on the master side
* and servicing entity spawn requests from the network on the proxy side, as
* well as detecting network availability and triggering network binding/unbinding.
*
* The system is first activated on the host side when OnNetworkSessionActivated event
* is received, and NetBindingSystemContextData is created.
* The system becomes fully operational when the NetBindingSystemContextData is activated
* and bound to the system, and remains operational as long as the NetBindingSystemContextData
* remains valid.
*
* Level switching is tracked by a monotonically increasing context sequence number controlled
* by the host. Spawn and bind operations are deferred until the correct sequence number
* is reached. Spawning is always performed from the game thread.
*/
class NetBindingSystemImpl
: public NetBindingSystemBus::Handler
, public NetBindingSystemEventsBus::Handler
, public EntityContextEventBus::Handler
, public AZ::TickBus::Handler
{
friend class NetBindingSystemContextData;
public:
NetBindingSystemImpl();
~NetBindingSystemImpl() override;
static void Reflect(AZ::ReflectContext* context);
virtual void Init();
virtual void Shutdown();
static const AZStd::chrono::milliseconds s_sliceBindingTimeout;
//////////////////////////////////////////////////////////////////////////
// NetBindingSystemBus
bool ShouldBindToNetwork() override;
NetBindingContextSequence GetCurrentContextSequence() override;
void AddReplicaMaster(AZ::Entity* entity, GridMate::ReplicaPtr replica) override;
AZ::EntityId GetStaticIdFromEntityId(AZ::EntityId entity) override;
AZ::EntityId GetEntityIdFromStaticId(AZ::EntityId staticEntityId) override;
void SpawnEntityFromSlice(GridMate::ReplicaId bindTo, const NetBindingSliceContext& bindToContext) override;
void SpawnEntityFromStream(AZ::IO::GenericStream& spawnData, AZ::EntityId useEntityId, GridMate::ReplicaId bindTo, NetBindingContextSequence addToContext) override;
void OnNetworkSessionActivated(GridMate::GridSession* session) override;
void OnNetworkSessionDeactivated(GridMate::GridSession* session) override;
void UnbindGameEntity(AZ::EntityId entity, const AZ::SliceComponent::SliceInstanceId& sliceInstanceId) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// EntityContextEventBus::Handler
void OnEntityContextReset() override;
void OnEntityContextLoadedFromStream(const AZ::SliceComponent::EntityList& contextEntities) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// TickBus::Handler
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
//////////////////////////////////////////////////////////////////////////
protected:
//! Called by the NetBindingContext chunk when it is activated
void OnContextDataActivated(GridMate::ReplicaChunkPtr contextData);
//! Called by the NetBindingContext chunk when it is deactivated
void OnContextDataDeactivated(GridMate::ReplicaChunkPtr contextData);
//! Update the current binding context sequence
virtual void UpdateContextSequence();
//! Process pending spawn requests
virtual void ProcessSpawnRequests();
//! Process pending bind requests
virtual void ProcessBindRequests();
//! Performs final stage of entity spawning process
virtual bool BindAndActivate(AZ::Entity* entity, GridMate::ReplicaId replicaId, bool addToContext, const AZ::SliceComponent::SliceInstanceId& sliceInstanceId);
//! Called on the host to spawn the net binding system replica
virtual GridMate::Replica* CreateSystemReplica();
AZ_FORCE_INLINE bool ReadyToAddReplica() const;
class SpawnRequest
{
public:
GridMate::ReplicaId m_bindTo;
AZ::EntityId m_useEntityId;
AZStd::vector<AZ::u8> m_spawnDataBuffer;
};
typedef AZStd::list<SpawnRequest> SpawnRequestContainerType;
typedef AZStd::map<NetBindingContextSequence, SpawnRequestContainerType> SpawnRequestContextContainerType;
typedef AZStd::unordered_map<AZ::SliceComponent::SliceInstanceId, NetBindingSliceInstantiationHandler> SliceRequestContainerType;
typedef AZStd::map<NetBindingContextSequence, SliceRequestContainerType> BindRequestContextContainerType;
GridMate::GridSession* m_bindingSession;
GridMate::ReplicaChunkPtr m_contextData;
NetBindingContextSequence m_currentBindingContextSequence;
SpawnRequestContextContainerType m_spawnRequests;
BindRequestContextContainerType m_bindRequests;
AZStd::list<AZStd::pair<AZ::EntityId, GridMate::ReplicaPtr>> m_addMasterRequests;
/**
* \brief override how root slice entities' replicas should be loaded
*
* We occasionally get GameContextBridge replica (that tells us what level to load) before we get
* a replica that tells us that we are connecting to a network sessions, thus we may not figure out in time if we
* need to load the root slice entities with NetBindingComponent as master replicas or proxy replicas.
* This is a fix until proper order is established.
*
* \param isAuthoritative true if root slice entities with NetBindingComponents to be loaded authoritatively
*/
void OverrideRootSliceLoadMode(bool isAuthoritative)
{
m_isAuthoritativeRootSliceLoad = isAuthoritative;
m_overrideRootSliceLoadAuthoritative = true;
}
private:
/**
* \brief True if the root slice is to be loaded authoritatively
*/
bool m_isAuthoritativeRootSliceLoad;
/**
* \brief True if root slice loading mode was overriden, otherwise the mode would be determined via m_bindingSession
*/
bool m_overrideRootSliceLoadAuthoritative;
/**
* \brief A helper method to figure the mode of loading root slice entities' replicas
* \return True if the root slice entities is to be loaded authoritatively
*/
bool IsAuthoritateLoad() const;
void UpdateClock(float deltaTime);
AZStd::chrono::system_clock::time_point Now() const;
AZStd::chrono::system_clock::time_point m_currentTime;
};
class NetBindingSystemContextData
: public GridMate::ReplicaChunk
{
public:
AZ_CLASS_ALLOCATOR(NetBindingSystemContextData, AZ::SystemAllocator, 0);
static const char* GetChunkName() { return "NetBindingSystemContextData"; }
NetBindingSystemContextData();
bool IsReplicaMigratable() override { return true; }
bool IsBroadcast() override { return true; }
void OnReplicaActivate(const GridMate::ReplicaContext& rc) override;
void OnReplicaDeactivate(const GridMate::ReplicaContext& rc) override;
GridMate::DataSet<AZ::u32, GridMate::VlqU32Marshaler> m_bindingContextSequence;
};
} // namespace AzFramework
@@ -1,38 +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>
namespace AzFramework
{
class NetworkContext;
/**
* The NetSystemRequestBus services requests for global networking systems in AzFramework
*/
class NetSystemRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
NetSystemRequests() = default;
virtual ~NetSystemRequests() = default;
virtual NetworkContext* GetNetworkContext() = 0;
};
using NetSystemRequestBus = AZ::EBus<NetSystemRequests>;
}
@@ -1,378 +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.
*
*/
#include <AzFramework/Network/NetworkContext.h>
#include <AzFramework/Network/NetBindable.h>
#include <GridMate/Replica/DataSet.h>
namespace AzFramework
{
NetworkContext::DescBase::DescBase(const char* name, ptrdiff_t offset)
: m_name(name)
, m_offset(offset)
{
}
NetworkContext::FieldDescBase::FieldDescBase(const char* name, ptrdiff_t offset)
: DescBase(name, offset)
, m_dataSetIdx(static_cast<size_t>(-1))
{
}
NetworkContext::RpcDescBase::RpcDescBase(const char* name, ptrdiff_t offset)
: DescBase(name, offset)
, m_rpcIdx(static_cast<size_t>(-1))
{
}
NetworkContext::CtorDataBase::CtorDataBase(const char* name)
: m_name(name)
{
}
NetworkContext::ClassBuilder::ClassBuilder(NetworkContext* context, ClassDescPtr binding)
: m_binding(binding)
, m_context(context)
{
}
NetworkContext::ClassBuilder::~ClassBuilder()
{
if (m_context->IsRemovingReflection())
{
if (m_binding->UnregisterChunkType)
{
m_binding->UnregisterChunkType();
}
}
else
{
if (m_binding->RegisterChunkType)
{
m_binding->RegisterChunkType();
}
}
}
NetworkContext::ClassDesc::ClassDesc(const char* name, const AZ::Uuid& typeId /* = AZ::Uuid() */)
: m_name(name)
, m_typeId(typeId)
{
}
///////////////////////////////////////////////////////////////////////////
/// NetworkContext
///////////////////////////////////////////////////////////////////////////
NetworkContext::NetworkContext()
{
}
NetworkContext::~NetworkContext()
{
}
size_t NetworkContext::GetReflectedChunkSize(const AZ::Uuid& typeId) const
{
size_t totalSize = 0;
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
ClassDescPtr binding = it->second;
for (const auto& field : binding->m_chunkDesc.m_fields)
{
totalSize += field->GetDataSetSize();
}
for (const auto& rpc : binding->m_chunkDesc.m_rpcs)
{
totalSize += rpc->GetRpcSize();
}
}
return totalSize;
}
bool NetworkContext::UsesSelfAsChunk(const AZ::Uuid& typeId) const
{
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
ClassDescPtr binding = it->second;
return !binding->m_chunkDesc.m_external && binding->m_chunkDesc.m_fields.size() > 0;
}
return false;
}
bool NetworkContext::UsesExternalChunk(const AZ::Uuid& typeId) const
{
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
ClassDescPtr binding = it->second;
return binding->m_chunkDesc.m_external && (AZ::u32(binding->m_chunkDesc.m_chunkId) != 0);
}
return false;
}
ReplicaChunkBase* NetworkContext::CreateReplicaChunk(const AZ::Uuid& typeId)
{
const auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
const ClassDescPtr binding = it->second;
if (binding->CreateReplicaChunk)
{
ReplicaChunkDescriptor* descriptor = ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(binding->m_chunkDesc.m_chunkId);
AZ_Assert(descriptor, "NetworkContext cannot find replica chunk descriptor for %s. Did you remember to register the chunk type?", binding->m_name);
ReplicaChunkDescriptorTable::Get().BeginConstructReplicaChunk(descriptor);
ReplicaChunkBase* chunk = binding->CreateReplicaChunk();
ReplicaChunkDescriptorTable::Get().EndConstructReplicaChunk();
chunk->Init(descriptor);
return chunk;
}
}
/*
* Special case: empty declarations such as:
*
* static void Reflect() {
* ....
* NetworkContext->Class<MyComponent>();
* }
*
* Result in no ReplicaChunks being created. It's treated as a no-op. No replication will be performed.
*/
return nullptr;
}
void NetworkContext::DestroyReplicaChunk(ReplicaChunkBase* chunk)
{
ReplicaChunkClassId chunkId = chunk->GetDescriptor()->GetChunkTypeId();
auto it = m_chunkBindings.find(chunkId);
if (it != m_chunkBindings.end())
{
ClassDescPtr binding = it->second;
binding->DestroyReplicaChunk(chunk);
return;
}
AZ_Warning("NetworkContext", false, "DestroyReplicaChunk could not find a binding for %s", chunk->GetDescriptor()->GetChunkName());
}
void NetworkContext::Bind(NetBindable* instance, ReplicaChunkPtr chunk, NetworkContextBindMode mode)
{
const AZ::Uuid& typeId = instance->RTTI_GetType();
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
ClassDescPtr binding = it->second;
if (chunk)
{
ReplicaChunkClassId chunkId = chunk->GetDescriptor()->GetChunkTypeId();
AZ_Assert(binding->m_chunkDesc.m_chunkId == chunkId, "NetworkContext detected a type mismatch while trying to bind an instance to a ReplicaChunk");
if (binding->m_chunkDesc.m_chunkId == chunkId)
{
if (!binding->m_chunkDesc.m_external)
{
ReflectedReplicaChunkBase* refChunk = static_cast<ReflectedReplicaChunkBase*>(chunk.get());
refChunk->Bind(instance, mode);
}
}
}
else
{
if (binding->BindRpcs)
{
binding->BindRpcs(instance);
}
}
}
}
void NetworkContext::EnumerateFields(const ReplicaChunkClassId& chunkId, FieldVisitor visitor) const
{
auto it = m_chunkBindings.find(chunkId);
if (it != m_chunkBindings.end())
{
const ChunkDesc& chunkDesc = it->second->m_chunkDesc;
for (const auto& field : chunkDesc.m_fields)
{
visitor(field.get());
}
}
}
void NetworkContext::EnumerateFields(const AZ::Uuid& typeId, FieldVisitor visitor) const
{
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
const ChunkDesc& chunkDesc = it->second->m_chunkDesc;
for (const auto& field : chunkDesc.m_fields)
{
visitor(field.get());
}
}
}
void NetworkContext::EnumerateRpcs(const ReplicaChunkClassId& chunkId, RpcVisitor visitor) const
{
auto it = m_chunkBindings.find(chunkId);
if (it != m_chunkBindings.end())
{
const ChunkDesc& chunkDesc = it->second->m_chunkDesc;
for (const auto& rpc : chunkDesc.m_rpcs)
{
visitor(rpc.get());
}
}
}
void NetworkContext::EnumerateRpcs(const AZ::Uuid& typeId, RpcVisitor visitor) const
{
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
const ChunkDesc& chunkDesc = it->second->m_chunkDesc;
for (const auto& rpc : chunkDesc.m_rpcs)
{
visitor(rpc.get());
}
}
}
void NetworkContext::EnumerateCtorData(const ReplicaChunkClassId& chunkId, CtorVisitor visitor) const
{
auto it = m_chunkBindings.find(chunkId);
if (it != m_chunkBindings.end())
{
const ChunkDesc& chunkDesc = it->second->m_chunkDesc;
for (const auto& ctor : chunkDesc.m_ctors)
{
visitor(ctor.get());
}
}
}
void NetworkContext::EnumerateCtorData(const AZ::Uuid& typeId, CtorVisitor visitor) const
{
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
const ChunkDesc& chunkDesc = it->second->m_chunkDesc;
for (const auto& ctor : chunkDesc.m_ctors)
{
visitor(ctor.get());
}
}
}
///////////////////////////////////////////////////////////////////////////
ReflectedReplicaChunkBase::ReflectedReplicaChunkBase()
: m_ctorBuffer(GridMate::EndianType::IgnoreEndian, 0)
{
}
///////////////////////////////////////////////////////////////////////////
NetworkContextChunkDescriptor::NetworkContextChunkDescriptor(const char* name, size_t size, const AZ::Uuid& typeId)
: ReplicaChunkDescriptor(name, size)
, m_typeId(typeId)
{
}
ReplicaChunkBase* NetworkContextChunkDescriptor::CreateFromStream(UnmarshalContext& ctx)
{
AZ_Assert(!m_typeId.IsNull(), "No typeid associated with NetworkContextChunkDescriptor, cannot spawn Chunk");
if (!m_typeId.IsNull())
{
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_Assert(netContext, "No NetworkContext found while trying to construct ReflectedReplicaChunk");
ReplicaChunkBase* replicaChunk = netContext->CreateReplicaChunk(m_typeId);
if (ctx.m_hasCtorData && ctx.m_iBuf)
{
NetworkContextChunkDescriptor* netChunkDesc = static_cast<NetworkContextChunkDescriptor*>(replicaChunk->GetDescriptor());
if (netChunkDesc->IsAuto())
{
// copy each ctor data field into the ctor buffer
ReflectedReplicaChunkBase* refChunk = static_cast<ReflectedReplicaChunkBase*>(replicaChunk);
netContext->EnumerateCtorData(m_typeId,
[&ctx, refChunk](NetworkContext::CtorDataBase* ctorData)
{
ctorData->Copy(*ctx.m_iBuf, refChunk->m_ctorBuffer);
});
}
}
return replicaChunk;
}
return nullptr;
}
void NetworkContextChunkDescriptor::DeleteReplicaChunk(ReplicaChunkBase* chunk)
{
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_Assert(netContext, "No NetworkContext found while trying to destroy ReflectedReplicaChunk");
netContext->DestroyReplicaChunk(chunk);
}
void NetworkContextChunkDescriptor::MarshalCtorData(ReplicaChunkBase* chunk, WriteBuffer& buffer)
{
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_Assert(netContext, "No NetworkContext found while trying to collect ctor data for ReflectedReplicaChunk");
NetBindable* netBindable = static_cast<NetBindable*>(chunk->GetHandler());
NetworkContextChunkDescriptor* netChunkDesc = static_cast<NetworkContextChunkDescriptor*>(chunk->GetDescriptor());
if (!netChunkDesc->IsAuto())
{
return;
}
if (netBindable) // chunk is bound, get source data from the netBindable
{
netContext->EnumerateCtorData(m_typeId,
[netBindable, &buffer](NetworkContext::CtorDataBase* ctorData)
{
ctorData->Marshal(netBindable, buffer);
});
}
else // chunk is not bound yet, copy the ctor data for forwarding
{
ReflectedReplicaChunkBase* refChunk = static_cast<ReflectedReplicaChunkBase*>(chunk);
ReadBuffer src(refChunk->m_ctorBuffer.GetEndianType(), refChunk->m_ctorBuffer.Get(), refChunk->m_ctorBuffer.Size());
netContext->EnumerateCtorData(m_typeId,
[&src, &buffer](NetworkContext::CtorDataBase* ctorData)
{
ctorData->Copy(src, buffer);
});
}
}
void NetworkContextChunkDescriptor::DiscardCtorStream(UnmarshalContext& ctx)
{
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_Assert(netContext, "No NetworkContext found while trying to skip ctor data for ReflectedReplicaChunk");
if (ctx.m_hasCtorData)
{
// Iterate over all of the ctor data and unmarshal it with no destination,
// which will advance the buffer past the ctor data for this object
netContext->EnumerateCtorData(m_typeId,
[&ctx](NetworkContext::CtorDataBase* ctorData)
{
ctorData->Unmarshal(*ctx.m_iBuf, nullptr);
});
}
}
}
@@ -1,969 +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/Memory/SystemAllocator.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzFramework/Network/NetSystemBus.h>
#include <AzFramework/Network/NetBindable.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/typetraits/is_base_of.h>
#include <AzCore/std/functional.h>
namespace AzFramework
{
class NetBindable;
using GridMate::ReplicaChunkInterface;
using GridMate::ReplicaChunkBase;
using GridMate::ReplicaChunk;
using GridMate::ReplicaChunkDescriptor;
using GridMate::DefaultReplicaChunkDescriptor;
using GridMate::ReplicaChunkDescriptorTable;
using GridMate::ReplicaChunkClassId;
using GridMate::ReplicaChunkPtr;
using GridMate::Rpc;
using GridMate::ZoneMask;
using GridMate::ZoneMask_All;
using GridMate::UnmarshalContext;
using GridMate::ReadBuffer;
using GridMate::WriteBuffer;
using GridMate::WriteBufferDynamic;
///////////////////////////////////////////////////////////////////////////
// GridMate ReplicaChunk/ReplicaChunkDescriptors
///////////////////////////////////////////////////////////////////////////
class ReflectedReplicaChunkBase
: public ReplicaChunkBase
, public ReplicaChunkInterface
{
friend NetworkContext;
public:
ReflectedReplicaChunkBase();
bool IsReplicaMigratable() override { return true; }
/// Returns the chunk type name, e.g. "ReflectedReplicaChunk<MyClass>"
virtual const char* GetName() const = 0;
/// Returns the linear size of the chunk including DataSets and RPCs
virtual size_t GetSize() const = 0;
/// Returns a pointer to the start of the DataSet/RPC storage allocated with the chunk
virtual AZ::u8* GetDataStart() const = 0;
/// Binds an instance of the reflected class to this chunk
virtual void Bind(NetBindable* instance, NetworkContextBindMode mode) = 0;
/// Removes network bindings from the bound NetBindable
virtual void Unbind() = 0;
WriteBufferDynamic m_ctorBuffer; ///< Buffer to hold ctor data before the chunk is bound
};
/// This will be the header for a blob in memory:
/// The layout looks like:
/// * ReflectedReplicaChunk<T>
/// * DataSets
/// * RPCs
template <class ClassType>
class ReflectedReplicaChunk
: public ReflectedReplicaChunkBase
{
friend NetworkContext;
public:
static const char* GetChunkName();
static size_t GetChunkSize();
public:
AZ_CLASS_ALLOCATOR(ReflectedReplicaChunk, AZ::SystemAllocator, 0);
ReflectedReplicaChunk()
: m_dataSets(reinterpret_cast<AZ::u8*>(this) + sizeof(*this))
{
}
const char* GetName() const override { return GetChunkName(); }
size_t GetSize() const override { return GetChunkSize(); }
AZ::u8* GetDataStart() const override { return const_cast<AZ::u8*>(m_dataSets); }
void Bind(NetBindable* instance, NetworkContextBindMode mode) override;
void Unbind() override;
private:
const AZ::u8* m_dataSets; ///< Points to the beginning of the datasets for this chunk
};
class NetworkContextChunkDescriptor
: public ReplicaChunkDescriptor
{
public:
NetworkContextChunkDescriptor(const char* name, size_t size, const AZ::Uuid& typeId = AZ::Uuid());
ReplicaChunkBase* CreateFromStream(UnmarshalContext& ctx) override;
void DeleteReplicaChunk(ReplicaChunkBase* chunkInstance) override;
void DiscardCtorStream(UnmarshalContext&) override;
void MarshalCtorData(ReplicaChunkBase*, WriteBuffer&) override;
void Bind(const AZ::Uuid& typeId) { m_typeId = typeId; }
virtual bool IsAuto() const { return false; }
private:
AZ::Uuid m_typeId; ///< TypeId of the class this descriptor represents (not the chunk type)
};
template <class ClassType, ZoneMask mask = ZoneMask_All>
class AutoChunkDescriptor
: public NetworkContextChunkDescriptor
{
public:
AutoChunkDescriptor()
: NetworkContextChunkDescriptor(ReflectedReplicaChunk<ClassType>::GetChunkName(), ReflectedReplicaChunk<ClassType>::GetChunkSize(), AZ::RttiTypeId<ClassType>())
{
}
ZoneMask GetZoneMask() const override { return mask; }
bool IsAuto() const override { return true; }
};
template <class ChunkType, ZoneMask mask = ZoneMask_All>
class ExternalChunkDescriptor
: public NetworkContextChunkDescriptor
{
public:
ExternalChunkDescriptor()
: NetworkContextChunkDescriptor(ChunkType::GetChunkName(), sizeof(ChunkType))
{}
ZoneMask GetZoneMask() const override { return mask; }
};
///////////////////////////////////////////////////////////////////////////
/// NetworkContext can be used to reflect classes for network serialization
/// It will automatically generate ReplicaChunks and bind them to instances
/// when requested. It also serves as a binding registry for binding a class
/// to the ReplicaChunk that should be used to replicate it.
///////////////////////////////////////////////////////////////////////////
class NetworkContext
: public AZ::ReflectContext
{
public:
/// @cond EXCLUDE_DOCS
class ClassBuilder;
class ClassDesc;
using ClassDescPtr = AZStd::intrusive_ptr<ClassDesc>;
using ClassBuilderPtr = AZStd::intrusive_ptr<ClassBuilder>;
using ClassBindings = AZStd::unordered_map<AZ::Uuid, ClassDescPtr>;
using ChunkBindings = AZStd::unordered_map<ReplicaChunkClassId, ClassDescPtr>;
using ClassInfo = ClassBuilder; ///< @deprecated Use NetworkContext::ClassBuilder
using ClassInfoPtr = ClassBuilderPtr; ///< @deprecated Use NetworkContext::ClassBuilderPtr
/// @endcond
class IntrusiveRefCounted
{
public:
virtual ~IntrusiveRefCounted() {}
private:
// refcount
template<class T>
friend struct AZStd::IntrusivePtrCountPolicy;
mutable unsigned int m_refCount = 0;
AZ_FORCE_INLINE void add_ref() { ++m_refCount; }
AZ_FORCE_INLINE void release()
{
AZ_Assert(m_refCount > 0, "Reference count logic error, trying to remove reference when refcount is 0");
if (--m_refCount == 0)
{
delete this;
}
}
};
/**
* Interface for recording classes, chunks, and datasets
* When destructed at the end of reflection, it will register/unregister the ChunkDescriptor
*/
class ClassBuilder
: public IntrusiveRefCounted
{
friend class NetworkContext;
protected:
AZ_CLASS_ALLOCATOR(ClassBuilder, AZ::SystemAllocator, 0);
ClassBuilder(NetworkContext* context, ClassDescPtr binding);
public:
~ClassBuilder();
ClassBuilderPtr operator->() { return this; }
/// Bind a ReplicaChunk type to this class for network serialization
template <class ChunkType, typename DescriptorType = ExternalChunkDescriptor<ChunkType> >
ClassBuilderPtr Chunk();
/// Bind a NetBindable's Field
template <class ClassType, typename FieldType>
typename AZStd::enable_if<AZStd::is_base_of<NetBindableFieldBase, FieldType>::value, ClassBuilderPtr>::type
Field(const char* name, FieldType ClassType::* address);
/// Declare an external chunk's DataSet
template <class ClassType, typename DataSetType>
typename AZStd::enable_if<AZStd::is_base_of<DataSetBase, DataSetType>::value, ClassBuilderPtr>::type
Field(const char* name, DataSetType ClassType::* address);
/// Bind an Rpc::BindInterface for this chunk
template <class ClassType, // class this RPC is part of
class InterfaceType = ClassType, // class implementing the RPC, must derive from ReplicaChunkInterface
typename ... Args,
class Traits = RpcDefaultTraits,
typename RpcBindType = typename Rpc<Args...>::template BindInterface<InterfaceType, bool (InterfaceType::*)(typename Args::Type..., const RpcContext&), Traits> >
typename AZStd::enable_if<AZStd::is_base_of<RpcBase, RpcBindType>::value, ClassBuilderPtr>::type
RPC(const char* name, RpcBindType ClassType::* rpc);
/// Bind a NetBindable::Rpc for this NetBindable
template <class ClassType,
class InterfaceType = ClassType,
typename ... Args,
class Traits = RpcDefaultTraits,
typename RpcBindType = typename NetBindable::Rpc<Args...>::template Bind<InterfaceType, bool (InterfaceType::*)(Args..., const RpcContext&), Traits> >
typename AZStd::enable_if<AZStd::is_base_of<NetBindableRpcBase, RpcBindType>::value, ClassBuilderPtr>::type
RPC(const char* name, RpcBindType ClassType::* rpc);
#define CTOR_DATA_OVERLOAD(_getsig, _setsig) \
template <class ClassType, class DataType, typename MarshalerType = Marshaler<DataType> > \
ClassBuilderPtr CtorData(const char* name, _getsig, _setsig, const MarshalerType&marshaler = MarshalerType()) \
{ \
return CtorDataImpl<ClassType, DataType>(name, getter, setter, marshaler); \
}
/// Bind a getter/setter pair for data required during object construction
// this has to be done via overload so that the user does not have to explicitly provide
// the template arguments, they can be divined from the function call
CTOR_DATA_OVERLOAD(DataType(ClassType::* getter)(), void (ClassType::* setter)(const DataType&));
CTOR_DATA_OVERLOAD(DataType(ClassType::* getter)() const, void (ClassType::* setter)(const DataType&));
CTOR_DATA_OVERLOAD(DataType & (ClassType::* getter)(), void (ClassType::* setter)(const DataType&));
CTOR_DATA_OVERLOAD(DataType & (ClassType::* getter)() const, void (ClassType::* setter)(const DataType&));
CTOR_DATA_OVERLOAD(const DataType&(ClassType::* getter)(), void (ClassType::* setter)(const DataType&));
CTOR_DATA_OVERLOAD(const DataType&(ClassType::* getter)() const, void (ClassType::* setter)(const DataType&));
CTOR_DATA_OVERLOAD(DataType(ClassType::* getter)(), void (ClassType::* setter)(DataType));
CTOR_DATA_OVERLOAD(DataType(ClassType::* getter)() const, void (ClassType::* setter)(DataType));
CTOR_DATA_OVERLOAD(DataType & (ClassType::* getter)(), void (ClassType::* setter)(DataType));
CTOR_DATA_OVERLOAD(DataType & (ClassType::* getter)() const, void (ClassType::* setter)(DataType));
CTOR_DATA_OVERLOAD(const DataType&(ClassType::* getter)(), void (ClassType::* setter)(DataType));
CTOR_DATA_OVERLOAD(const DataType&(ClassType::* getter)() const, void (ClassType::* setter)(DataType));
#undef CTOR_DATA_OVERLOAD
private:
template <class ClassType,
class DataType,
class GetterFunction,
class SetterFunction,
typename MarshalerType = Marshaler<DataType> >
ClassBuilderPtr CtorDataImpl(const char* name, GetterFunction getter, SetterFunction setter, const MarshalerType& marshaler = MarshalerType());
private:
ClassDescPtr m_binding;
NetworkContext* m_context;
};
class DescBase
: public IntrusiveRefCounted
{
friend class NetworkContext;
public:
AZ_CLASS_ALLOCATOR(DescBase, AZ::SystemAllocator, 0);
DescBase(const char* name, ptrdiff_t offset);
virtual ~DescBase() {}
const char* GetName() const { return m_name; }
ptrdiff_t GetOffset() const { return m_offset; }
protected:
const char* m_name; ///< Field name, will be used as DataSet debug name
ptrdiff_t m_offset; ///< Offset from an instance pointer (a ReplicaChunk or the actual class instance)
};
class FieldDescBase
: public DescBase
{
friend class NetworkContext;
public:
AZ_CLASS_ALLOCATOR(FieldDescBase, AZ::SystemAllocator, 0);
FieldDescBase(const char* name, ptrdiff_t offset);
virtual ~FieldDescBase() {}
virtual void ConstructDataSet(void*) const = 0;
virtual void DestructDataSet(void*) const = 0;
virtual size_t GetDataSetSize() const = 0;
size_t GetDataSetIndex() const { return m_dataSetIdx; }
protected:
size_t m_dataSetIdx;
};
/**
* Represents a DataSet in a chunk or class
* NOTE: m_offset in this class is the offset from ReplicaChunk* -> DataSet
*/
template <typename DataSetType>
class DataSetDesc
: public FieldDescBase
{
public:
AZ_CLASS_ALLOCATOR(DataSetDesc, AZ::SystemAllocator, 0);
DataSetDesc(const char* name, ptrdiff_t offset);
void ConstructDataSet(void*) const override {}
void DestructDataSet(void*) const override {}
size_t GetDataSetSize() const override { return sizeof(DataSetType); }
};
/**
* Represents a field in a chunk, responsible for creating a DataSet<T, Marshaler, Throttler>
* that represents the field
* NOTE: m_offset in this class is the offset from NetBindable* -> NetBindable::Field
*/
template <typename FieldType>
class NetBindableFieldDesc
: public FieldDescBase
{
public:
using DataSetType = typename FieldType::DataSetType;
public:
AZ_CLASS_ALLOCATOR(NetBindableFieldDesc, AZ::SystemAllocator, 0);
NetBindableFieldDesc(const char* name, ptrdiff_t offset);
void ConstructDataSet(void* mem) const override { FieldType::ConstructDataSet(mem, m_name); }
void DestructDataSet(void* mem) const override { FieldType::DestructDataSet(mem); }
size_t GetDataSetSize() const override { return sizeof(DataSetType); }
};
class RpcDescBase
: public DescBase
{
friend class NetworkContext;
public:
AZ_CLASS_ALLOCATOR(RpcDescBase, AZ::SystemAllocator, 0);
RpcDescBase(const char* name, ptrdiff_t offset);
virtual ~RpcDescBase() {}
virtual void ConstructRpc(void*) const {}
virtual void DestructRpc(void*) const {}
virtual size_t GetRpcSize() const { return 0; }
size_t GetRpcIndex() const { return m_rpcIdx; }
protected:
size_t m_rpcIdx;
};
template <typename RpcBindType>
class NetBindableRpcDesc
: public RpcDescBase
{
friend class NetworkContext;
public:
AZ_CLASS_ALLOCATOR(NetBindableRpcDesc, AZ::SystemAllocator, 0);
NetBindableRpcDesc(const char* name, ptrdiff_t offset)
: RpcDescBase(name, offset)
{
static_assert((AZStd::is_base_of<NetBindableRpcBase, RpcBindType>::value), "NetBindableRpcDesc is intended for use only with NetBindableRpcs");
}
void ConstructRpc(void* mem) const override { RpcBindType::ConstructRpc(mem, m_name); }
void DestructRpc(void* mem) const override { RpcBindType::DestructRpc(mem); }
size_t GetRpcSize() const override { return sizeof(typename RpcBindType::BindInterfaceType); }
};
class CtorDataBase
: public IntrusiveRefCounted
{
public:
AZ_CLASS_ALLOCATOR(CtorDataBase, AZ::SystemAllocator, 0);
CtorDataBase(const char* name);
virtual ~CtorDataBase() {}
virtual void Marshal(NetBindable* netBindable, WriteBuffer& buffer) const = 0;
virtual void Unmarshal(ReadBuffer& buffer, NetBindable* netBindable) const = 0;
virtual void Copy(ReadBuffer& src, WriteBuffer& dest) const = 0;
protected:
const char* m_name;
};
template <class ClassType, class DataType, typename MarshalerType>
class CtorDataDesc
: public CtorDataBase
{
using GetterFunction = AZStd::function<DataType(ClassType*)>;
using SetterFunction = AZStd::function<void (ClassType*, const DataType&)>;
public:
AZ_CLASS_ALLOCATOR(CtorDataDesc, AZ::SystemAllocator, 0);
CtorDataDesc(const char* name, GetterFunction get, SetterFunction set)
: CtorDataBase(name)
, m_get(get)
, m_set(set)
{}
CtorDataDesc(const char* name, DataType(ClassType::* getter)(), void (ClassType::* setter)(const DataType&))
: CtorDataBase(name)
, m_get(AZStd::bind(getter, AZStd::placeholders::_1))
, m_set(AZStd::bind(setter, AZStd::placeholders::_1, AZStd::placeholders::_2))
{}
void Marshal(NetBindable* netBindable, WriteBuffer& buffer) const override;
void Unmarshal(ReadBuffer& buffer, NetBindable* netBindable) const override;
virtual void Copy(ReadBuffer& src, WriteBuffer& dest) const override;
GetterFunction m_get;
SetterFunction m_set;
MarshalerType m_marshaler;
};
struct ChunkDesc
{
public:
using Fields = AZStd::vector<AZStd::intrusive_ptr<FieldDescBase> >;
using Rpcs = AZStd::vector<AZStd::intrusive_ptr<RpcDescBase> >;
using Ctors = AZStd::vector<AZStd::intrusive_ptr<CtorDataBase> >;
const char* m_name = nullptr; ///< The name of the chunk
ReplicaChunkClassId m_chunkId; ///< The registered id of the ReplicaChunk this class will use
Fields m_fields; ///< list of data fields in the ReplicaChunk
Rpcs m_rpcs; ///< list of RPCs in the ReplicaChunk
Ctors m_ctors; ///< list of ctor callbacks to gather/apply ctor data
bool m_external = false; ///< If true, this chunk is separate from the class bound to it
};
/**
* Contains the chunk factory and field descriptions for a given class
*/
class ClassDesc
: public IntrusiveRefCounted
{
public:
AZ_CLASS_ALLOCATOR(ClassDesc, AZ::SystemAllocator, 0);
ClassDesc(const char* name = nullptr, const AZ::Uuid& typeId = AZ::Uuid());
public:
const char* m_name; ///< The name of the class that is bound
AZ::Uuid m_typeId; ///< The type that this binding represents (null for chunks)
ChunkDesc m_chunkDesc; ///< Descriptor for the chunk for this type
/// Functor which will register the ReplicaChunkDescriptor with the global registry
AZStd::function<bool()> RegisterChunkType;
/// Functor to unregister the ReplicaChunkDescriptor (during reflection removal)
AZStd::function<void()> UnregisterChunkType;
/// Functor which will create a ReplicaChunk and bind it to the given instance
AZStd::function<ReplicaChunkBase*()> CreateReplicaChunk;
/// Functor which can destroy a ReplicaChunk and free its memory
AZStd::function<void(ReplicaChunkBase*)> DestroyReplicaChunk;
/// Functor which binds an instance of this class to its RPCs for local dispatch
AZStd::function<void(NetBindable* bindable)> BindRpcs;
};
AZ_CLASS_ALLOCATOR(NetworkContext, AZ::SystemAllocator, 0);
AZ_RTTI(NetworkContext, "{B1172D4A-EA1B-441D-AAE6-A9933DAECA8A}", AZ::ReflectContext);
NetworkContext();
virtual ~NetworkContext();
/// Register a class with the NetworkContext for replication
template <class ClassType>
ClassBuilderPtr Class();
/// Create a replica chunk for a given class
ReplicaChunkBase* CreateReplicaChunk(const AZ::Uuid& typeId);
/// Create a replica chunk for a given class, template version
template <class ClassType>
ReplicaChunkBase* CreateReplicaChunk();
/// Destroy a replica chunk for a given class
void DestroyReplicaChunk(ReplicaChunkBase * chunk);
/// Bind an instance and a chunk to each other
void Bind(NetBindable * instance, ReplicaChunkPtr chunk, NetworkContextBindMode mode);
/// Returns whether or not a given type uses a reflected (automatic) ReplicaChunk
bool UsesSelfAsChunk(const AZ::Uuid & typeId) const;
/// Returns whether or not a given type uses a custom ReplicaChunk
bool UsesExternalChunk(const AZ::Uuid & typeId) const;
/// Return the size of the the chunk which will represent the given type
size_t GetReflectedChunkSize(const AZ::Uuid & typeId) const;
using FieldVisitor = AZStd::function<void(FieldDescBase*)>;
void EnumerateFields(const ReplicaChunkClassId&chunkId, FieldVisitor visitor) const;
void EnumerateFields(const AZ::Uuid & typeId, FieldVisitor visitor) const;
using RpcVisitor = AZStd::function<void(RpcDescBase*)>;
void EnumerateRpcs(const ReplicaChunkClassId&chunkId, RpcVisitor visitor) const;
void EnumerateRpcs(const AZ::Uuid & typeId, RpcVisitor visitor) const;
using CtorVisitor = AZStd::function<void(CtorDataBase*)>;
void EnumerateCtorData(const ReplicaChunkClassId&chunkId, CtorVisitor visitor) const;
void EnumerateCtorData(const AZ::Uuid & typeId, CtorVisitor visitor) const;
private:
template <class ClassType>
void InitReflectedChunkBinding(ClassDescPtr binding);
template <class ChunkType, typename DescriptorType = ExternalChunkDescriptor<ChunkType> >
void InitExternalChunkBinding(ClassDescPtr binding);
private:
ClassBindings m_classBindings;
ChunkBindings m_chunkBindings;
};
///////////////////////////////////////////////////////////////////////////
template <class ClassType>
NetworkContext::ClassBuilderPtr NetworkContext::Class()
{
static_assert((AZStd::is_base_of<NetBindable, ClassType>::value), "Classes reflected through NetworkContext must be derived from NetBindable");
const AZ::Uuid& typeId = AZ::AzTypeInfo<ClassType>::Uuid();
ClassDescPtr binding = nullptr;
if (IsRemovingReflection()) // Just remove the entire class definition
{
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
binding = it->second;
m_chunkBindings.erase(binding->m_chunkDesc.m_chunkId);
m_classBindings.erase(it);
}
}
else
{
auto ret = m_classBindings.insert_key(typeId);
AZ_Assert(ret.second, "Cannot register more than one type with the same Uuid in the NetworkContext");
binding = ret.first->second = aznew ClassDesc(AZ::AzTypeInfo<ClassType>::Name(), AZ::AzTypeInfo<ClassType>::Uuid());
}
return aznew ClassBuilder(this, binding);
}
template <class ClassType>
void NetworkContext::InitReflectedChunkBinding(ClassDescPtr binding)
{
if (!binding->RegisterChunkType)
{
binding->m_chunkDesc.m_name = ReflectedReplicaChunk<ClassType>::GetChunkName();
ReplicaChunkClassId chunkClassId = ReplicaChunkClassId(binding->m_chunkDesc.m_name);
m_chunkBindings[chunkClassId] = binding;
NetworkContext* netContext = this;
binding->RegisterChunkType = [chunkClassId, netContext]()
{
bool result = ReplicaChunkDescriptorTable::Get().RegisterChunkType<ReflectedReplicaChunk<ClassType>, AutoChunkDescriptor<ClassType> >();
ReplicaChunkDescriptor* desc = ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(chunkClassId);
ReplicaChunkDescriptorTable::Get().BeginConstructReplicaChunk(desc);
// The offset recorded in NetBindableFields is the offset in the NetBindable
// We must compute the offset of the generated DataSets here and record the
// index from the descriptor
ptrdiff_t offset = sizeof(ReflectedReplicaChunk<ClassType>); // data sets are right after the ReflectedReplicaChunk<> in memory
netContext->EnumerateFields(chunkClassId,
[desc, &offset](FieldDescBase* field)
{
desc->RegisterDataSet(field->m_name, offset);
field->m_dataSetIdx = desc->GetDataSetIndex(offset);
offset += field->GetDataSetSize();
});
netContext->EnumerateRpcs(chunkClassId,
[desc, &offset](RpcDescBase* rpc)
{
desc->RegisterRPC(rpc->m_name, offset);
rpc->m_rpcIdx = desc->GetRpcIndex(offset);
offset += rpc->GetRpcSize();
});
AZ_Assert(offset == static_cast<ptrdiff_t>(ReflectedReplicaChunk<ClassType>::GetChunkSize()), "Overflow/underflow while registering DataSets for %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
ReplicaChunkDescriptorTable::Get().EndConstructReplicaChunk();
return result;
};
binding->UnregisterChunkType = [chunkClassId]()
{
ReplicaChunkDescriptorTable::Get().UnregisterReplicaChunkDescriptor(chunkClassId);
};
binding->CreateReplicaChunk = [netContext, chunkClassId]()
{
ReflectedReplicaChunkBase* chunk = new(azmalloc(ReflectedReplicaChunk<ClassType>::GetChunkSize(), AZStd::alignment_of<ReflectedReplicaChunk<ClassType> >::value, AZ::SystemAllocator, ReflectedReplicaChunk<ClassType>::GetChunkName()))ReflectedReplicaChunk<ClassType>();
AZ::u8* dataStart = chunk->GetDataStart();
AZ::u8* dataEnd = reinterpret_cast<AZ::u8*>(chunk) + chunk->GetSize();
ptrdiff_t offset = 0;
netContext->EnumerateFields(chunkClassId,
[&offset, dataStart, dataEnd](FieldDescBase* field)
{
AZ_Assert((dataStart + offset) < dataEnd, "Overflow in NetworkContext::CreateReplicaChunk while creating %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
void* dataSetMem = reinterpret_cast<void*>(dataStart + offset);
field->ConstructDataSet(dataSetMem);
offset += field->GetDataSetSize();
});
netContext->EnumerateRpcs(chunkClassId,
[&offset, dataStart, dataEnd](RpcDescBase* rpc)
{
AZ_Assert((dataStart + offset) < dataEnd, "Overflow in NetworkContext::CreateReplicaChunk while creating %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
void* rpcMem = reinterpret_cast<void*>(dataStart + offset);
rpc->ConstructRpc(rpcMem);
offset += rpc->GetRpcSize();
});
AZ_Assert((dataStart + offset) == dataEnd, "Overflow/underflow in NetworkContext::CreateReplicaChunk while creating %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
return chunk;
};
binding->DestroyReplicaChunk = [netContext, chunkClassId](ReplicaChunkBase* chunkBase)
{
AZ_Assert(chunkBase->GetDescriptor()->GetChunkTypeId() == chunkClassId, "Mismatched chunk type id for %s (0x%p)", ReflectedReplicaChunk<ClassType>::GetChunkName(), chunkBase);
ReflectedReplicaChunkBase* chunk = static_cast<ReflectedReplicaChunkBase*>(chunkBase);
chunk->Unbind();
AZ::u8* dataStart = chunk->GetDataStart();
AZ::u8* dataEnd = reinterpret_cast<AZ::u8*>(chunk) + chunk->GetSize();
ptrdiff_t offset = 0;
netContext->EnumerateFields(chunkClassId,
[&offset, dataStart, dataEnd](FieldDescBase* field)
{
AZ_Assert((dataStart + offset) < dataEnd, "Overflow in dtor while destroying %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
void* dataSetMem = reinterpret_cast<void*>(dataStart + offset);
field->DestructDataSet(dataSetMem);
offset += field->GetDataSetSize();
});
netContext->EnumerateRpcs(chunkClassId,
[&offset, dataStart, dataEnd](RpcDescBase* rpc)
{
AZ_Assert((dataStart + offset) < dataEnd, "Overflow in NetworkContext::CreateReplicaChunk while creating %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
void* rpcMem = reinterpret_cast<void*>(dataStart + offset);
rpc->DestructRpc(rpcMem);
offset += rpc->GetRpcSize();
});
AZ_Assert((dataStart + offset) == dataEnd, "Overflow/underflow in dtor while destroying %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
chunk->~ReflectedReplicaChunkBase();
azfree(chunk, AZ::SystemAllocator, ReflectedReplicaChunk<ClassType>::GetChunkSize(), AZStd::alignment_of<ReflectedReplicaChunk<ClassType> >::value);
};
binding->BindRpcs = [netContext, chunkClassId](NetBindable* bindable)
{
ClassType* derivedInstance = static_cast<ClassType*>(bindable);
netContext->EnumerateRpcs(chunkClassId,
[derivedInstance](const RpcDescBase* rpc)
{
NetBindableRpcBase* bindableRpc = reinterpret_cast<NetBindableRpcBase*>(reinterpret_cast<AZ::u8*>(derivedInstance) + rpc->GetOffset());
bindableRpc->Bind(derivedInstance);
});
};
binding->m_chunkDesc.m_chunkId = chunkClassId;
}
}
template <class ChunkType, typename DescriptorType>
void NetworkContext::InitExternalChunkBinding(ClassDescPtr binding)
{
if (!binding->RegisterChunkType)
{
binding->m_chunkDesc.m_name = ChunkType::GetChunkName();
ReplicaChunkClassId chunkClassId = ReplicaChunkClassId(binding->m_chunkDesc.m_name);
m_chunkBindings[chunkClassId] = binding;
const AZ::Uuid& typeId = binding->m_typeId;
NetworkContext* netContext = this;
binding->RegisterChunkType = [chunkClassId, typeId, netContext]()
{
bool result = ReplicaChunkDescriptorTable::Get().RegisterChunkType<ChunkType, DescriptorType>();
NetworkContextChunkDescriptor* desc = static_cast<NetworkContextChunkDescriptor*>(ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(chunkClassId));
desc->Bind(typeId);
netContext->EnumerateFields(chunkClassId,
[desc](FieldDescBase* field)
{
desc->RegisterDataSet(field->m_name, field->m_offset);
field->m_dataSetIdx = desc->GetDataSetIndex(field->m_offset);
});
netContext->EnumerateRpcs(chunkClassId,
[desc](RpcDescBase* rpc)
{
desc->RegisterRPC(rpc->m_name, rpc->m_offset);
});
return result;
};
binding->UnregisterChunkType = [chunkClassId]()
{
return ReplicaChunkDescriptorTable::Get().UnregisterReplicaChunkDescriptor(chunkClassId);
};
binding->CreateReplicaChunk = []()
{
return aznew ChunkType();
};
binding->DestroyReplicaChunk = [](ReplicaChunkBase* chunk)
{
delete chunk;
};
binding->m_chunkDesc.m_chunkId = chunkClassId;
}
}
template <class ClassType>
ReplicaChunkBase* NetworkContext::CreateReplicaChunk()
{
return CreateReplicaChunk(AZ::AzTypeInfo<ClassType>::Uuid());
}
///////////////////////////////////////////////////////////////////////////
template <class DataSetType>
NetworkContext::DataSetDesc<DataSetType>::DataSetDesc(const char* name, ptrdiff_t offset)
: NetworkContext::FieldDescBase(name, offset)
{
}
///////////////////////////////////////////////////////////////////////////
template <typename FieldType>
NetworkContext::NetBindableFieldDesc<FieldType>::NetBindableFieldDesc(const char* name, ptrdiff_t offset)
: NetworkContext::FieldDescBase(name, offset)
{
}
///////////////////////////////////////////////////////////////////////////
template <class ClassType, class DataType, typename MarshalerType>
void NetworkContext::CtorDataDesc<ClassType, DataType, MarshalerType>::Marshal(NetBindable* netBindable, WriteBuffer& buffer) const
{
ClassType* instance = static_cast<ClassType*>(netBindable);
DataType data = m_get(instance);
buffer.Write(data, m_marshaler);
}
template <class ClassType, class DataType, typename MarshalerType>
void NetworkContext::CtorDataDesc<ClassType, DataType, MarshalerType>::Unmarshal(ReadBuffer& buffer, NetBindable* netBindable) const
{
ClassType* instance = static_cast<ClassType*>(netBindable);
DataType data;
buffer.Read(data, m_marshaler);
if (instance)
{
m_set(instance, data);
}
}
template <class ClassType, class DataType, typename MarshalerType>
void NetworkContext::CtorDataDesc<ClassType, DataType, MarshalerType>::Copy(ReadBuffer& src, WriteBuffer& dest) const
{
DataType data;
src.Read(data, m_marshaler);
dest.Write(data, m_marshaler);
}
///////////////////////////////////////////////////////////////////////////
template <class ChunkType, typename DescriptorType>
NetworkContext::ClassBuilderPtr NetworkContext::ClassBuilder::Chunk()
{
if (!m_context->IsRemovingReflection())
{
static_assert((AZStd::is_base_of<ReplicaChunkBase, ChunkType>::value), "ReplicaChunks being registered with the NetworkContext must derive from ReplicaChunk");
static_assert((AZStd::is_base_of<NetworkContextChunkDescriptor, DescriptorType>::value), "Chunk bindings via NetworkContext must use a NetworkContextChunkDescriptor derived descriptor");
AZ_Assert(!m_binding->m_typeId.IsNull(), "Cannot register a ReplicaChunk for a class which has not been declared to the NetworkContext");
AZ_Assert(!m_binding->m_chunkDesc.m_chunkId, "Cannot register more than one ReplicaChunk binding for a class in the NetworkContext");
m_context->InitExternalChunkBinding<ChunkType, DescriptorType>(m_binding);
m_binding->m_chunkDesc.m_external = true;
}
return this;
}
template <class ClassType, typename FieldType>
typename AZStd::enable_if<AZStd::is_base_of<NetBindableFieldBase, FieldType>::value, NetworkContext::ClassBuilderPtr>::type
NetworkContext::ClassBuilder::Field(const char* name, FieldType ClassType::* address)
{
if (!m_context->IsRemovingReflection())
{
AZ_Assert(!m_binding->m_typeId.IsNull(), "Cannot register a field for a class which has not been declared to the NetworkContext");
AZ_Assert(!m_binding->m_chunkDesc.m_external, "Cannot register a NetBindable::Field from within an external chunk");
m_context->InitReflectedChunkBinding<ClassType>(m_binding);
ptrdiff_t offset = reinterpret_cast<ptrdiff_t>(&(reinterpret_cast<ClassType const volatile*>(0)->*address));
m_binding->m_chunkDesc.m_fields.push_back(aznew NetBindableFieldDesc<FieldType>(name, offset));
}
return this;
}
template <class ClassType, typename DataSetType>
typename AZStd::enable_if<AZStd::is_base_of<DataSetBase, DataSetType>::value, NetworkContext::ClassBuilderPtr>::type
NetworkContext::ClassBuilder::Field(const char* name, DataSetType ClassType::* address)
{
if (!m_context->IsRemovingReflection())
{
AZ_Assert(!m_binding->m_typeId.IsNull(), "Cannot register a field for a class which has not been declared to the NetworkContext");
ptrdiff_t offset = reinterpret_cast<ptrdiff_t>(&(reinterpret_cast<ClassType const volatile*>(0)->*address));
m_binding->m_chunkDesc.m_fields.push_back(aznew DataSetDesc<DataSetType>(name, offset));
}
return this;
}
template <class ClassType, class InterfaceType, typename ... Args, class Traits, typename RpcBindType>
typename AZStd::enable_if<AZStd::is_base_of<RpcBase, RpcBindType>::value, NetworkContext::ClassBuilderPtr>::type
NetworkContext::ClassBuilder::RPC(const char* name, RpcBindType ClassType::* rpc)
{
if (!m_context->IsRemovingReflection())
{
static_assert((AZStd::is_base_of<ReplicaChunkInterface, InterfaceType>::value), "Cannot bind an RPC call to an object which is not a ReplicaChunkInterface");
AZ_Assert(!m_binding->m_typeId.IsNull(), "Cannot register an RPC for a class which has not been declared to the NetworkContext");
ptrdiff_t offset = reinterpret_cast<ptrdiff_t>(&(reinterpret_cast<ClassType const volatile*>(0)->*rpc));
m_binding->m_chunkDesc.m_rpcs.push_back(aznew RpcDescBase(name, offset));
}
return this;
}
template <class ClassType, class InterfaceType, typename ... Args, class Traits, typename RpcBindType>
typename AZStd::enable_if<AZStd::is_base_of<NetBindableRpcBase, RpcBindType>::value, NetworkContext::ClassBuilderPtr>::type
NetworkContext::ClassBuilder::RPC(const char* name, RpcBindType ClassType::* rpc)
{
if (!m_context->IsRemovingReflection())
{
static_assert((AZStd::is_base_of<ReplicaChunkInterface, InterfaceType>::value), "Cannot bind an RPC call to an object which is not a ReplicaChunkInterface");
AZ_Assert(!m_binding->m_typeId.IsNull(), "Cannot register an RPC for a class which has not been declared to the NetworkContext");
m_context->InitReflectedChunkBinding<ClassType>(m_binding);
ptrdiff_t offset = reinterpret_cast<ptrdiff_t>(&(reinterpret_cast<ClassType const volatile*>(0)->*rpc));
m_binding->m_chunkDesc.m_rpcs.push_back(aznew NetBindableRpcDesc<RpcBindType>(name, offset));
}
return this;
}
template <class ClassType, class DataType, typename GetterFunction, typename SetterFunction, typename MarshalerType>
NetworkContext::ClassBuilderPtr NetworkContext::ClassBuilder::CtorDataImpl(const char* name, GetterFunction getter, SetterFunction setter, const MarshalerType&)
{
if (!m_context->IsRemovingReflection())
{
m_context->InitReflectedChunkBinding<ClassType>(m_binding);
auto get = [getter](NetBindable* nb) -> DataType { return (*static_cast<ClassType*>(nb).*getter)(); };
auto set = [setter](NetBindable* nb, const DataType& data) { (*static_cast<ClassType*>(nb).*setter)(data); };
m_binding->m_chunkDesc.m_ctors.push_back(aznew CtorDataDesc<ClassType, DataType, MarshalerType>(name, get, set));
}
return this;
}
///////////////////////////////////////////////////////////////////////////
template <class ClassType>
const char* ReflectedReplicaChunk<ClassType>::GetChunkName()
{
static char name[128] = { 0 };
if (!name[0])
{
AZ::Internal::AzTypeInfoSafeCat(name, AZ_ARRAY_SIZE(name), "ReflectedReplicaChunk<");
AZ::Internal::AzTypeInfoSafeCat(name, AZ_ARRAY_SIZE(name), AZ::AzTypeInfo<ClassType>::Name());
AZ::Internal::AzTypeInfoSafeCat(name, AZ_ARRAY_SIZE(name), ">");
}
return name;
}
template <class ClassType>
size_t ReflectedReplicaChunk<ClassType>::GetChunkSize()
{
static size_t chunkSize = 0;
if (chunkSize == 0)
{
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_Assert(netContext, "No NetworkContext found while trying to compute chunk size");
if (!netContext)
{
return 0;
}
chunkSize = sizeof(ReflectedReplicaChunk<ClassType>) + netContext->GetReflectedChunkSize(AZ::AzTypeInfo<ClassType>::Uuid());
}
return chunkSize;
}
template <class ClassType>
void ReflectedReplicaChunk<ClassType>::Bind(NetBindable* instance, NetworkContextBindMode mode)
{
SetHandler(instance);
ClassType* derivedInstance = azrtti_cast<ClassType*>(instance);
AZ_Assert(derivedInstance, "Unable to convert NetBindable to %s", AZ::AzTypeInfo<ClassType>::Name());
ReplicaChunkDescriptor* desc = GetDescriptor();
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
netContext->EnumerateFields(desc->GetChunkTypeId(),
[this, derivedInstance, desc, mode](NetworkContext::FieldDescBase* field)
{
NetBindableFieldBase* bindableField = reinterpret_cast<NetBindableFieldBase*>(reinterpret_cast<AZ::u8*>(derivedInstance) + field->GetOffset());
DataSetBase* dataSet = desc->GetDataSet(this, field->GetDataSetIndex());
bindableField->Bind(dataSet, mode);
});
netContext->EnumerateRpcs(desc->GetChunkTypeId(),
[this, derivedInstance, desc](NetworkContext::RpcDescBase* rpc)
{
NetBindableRpcBase* bindableRpc = reinterpret_cast<NetBindableRpcBase*>(reinterpret_cast<AZ::u8*>(derivedInstance) + rpc->GetOffset());
RpcBase* rpcBase = desc->GetRpc(this, rpc->GetRpcIndex());
bindableRpc->Bind(rpcBase);
});
// Transfer any stored ctor data from the buffer -> NetBindable instance
if (m_ctorBuffer.Size() > 0)
{
ReadBuffer ctorBuffer(m_ctorBuffer.GetEndianType(), m_ctorBuffer.Get(), m_ctorBuffer.Size());
netContext->EnumerateCtorData(desc->GetChunkTypeId(),
[instance, &ctorBuffer](NetworkContext::CtorDataBase* ctorData)
{
ctorData->Unmarshal(ctorBuffer, instance);
});
}
}
template <class ClassType>
void ReflectedReplicaChunk<ClassType>::Unbind()
{
ReplicaChunkInterface* handler = GetHandler();
if (!handler || handler == this)
{
return;
}
NetBindable* netBindable = static_cast<NetBindable*>(handler);
ClassType* derivedInstance = azrtti_cast<ClassType*>(netBindable);
AZ_Assert(derivedInstance, "Unable to convert NetBindable to %s. Have you forgotten to derive your component from AzFramework::NetBindable?", AZ::AzTypeInfo<ClassType>::Name());
if (derivedInstance)
{
ReplicaChunkDescriptor* desc = GetDescriptor();
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
netContext->EnumerateFields(desc->GetChunkTypeId(),
[derivedInstance](NetworkContext::FieldDescBase* field)
{
NetBindableFieldBase* bindableField = reinterpret_cast<NetBindableFieldBase*>(reinterpret_cast<AZ::u8*>(derivedInstance) + field->GetOffset());
bindableField->Bind(nullptr, NetworkContextBindMode::NonAuthoritative);
});
netContext->EnumerateRpcs(desc->GetChunkTypeId(),
[derivedInstance](NetworkContext::RpcDescBase* rpc)
{
NetBindableRpcBase* bindableRpc = reinterpret_cast<NetBindableRpcBase*>(reinterpret_cast<AZ::u8*>(derivedInstance) + rpc->GetOffset());
bindableRpc->Bind(derivedInstance);
});
}
// We have disconnected from the handler and erased any connections from DataFields or Rpcs
SetHandler(nullptr);
}
} // namespace AZ
@@ -99,6 +99,11 @@ namespace AzPhysics
classElement.RemoveElementByName(AZ_CRC_CE("Property Visibility Flags"));
}
if (classElement.GetVersion() <= 4)
{
classElement.RemoveElementByName(AZ_CRC_CE("Simulated"));
}
return true;
}
}
@@ -110,7 +115,7 @@ namespace AzPhysics
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<RigidBodyConfiguration, AzPhysics::SimulatedBodyConfiguration>()
->Version(4, &Internal::RigidBodyVersionConverter)
->Version(5, &Internal::RigidBodyVersionConverter)
->Field("Initial linear velocity", &RigidBodyConfiguration::m_initialLinearVelocity)
->Field("Initial angular velocity", &RigidBodyConfiguration::m_initialAngularVelocity)
->Field("Linear damping", &RigidBodyConfiguration::m_linearDamping)
@@ -119,7 +124,6 @@ namespace AzPhysics
->Field("Start Asleep", &RigidBodyConfiguration::m_startAsleep)
->Field("Interpolate Motion", &RigidBodyConfiguration::m_interpolateMotion)
->Field("Gravity Enabled", &RigidBodyConfiguration::m_gravityEnabled)
->Field("Simulated", &RigidBodyConfiguration::m_simulated)
->Field("Kinematic", &RigidBodyConfiguration::m_kinematic)
->Field("CCD Enabled", &RigidBodyConfiguration::m_ccdEnabled)
->Field("Compute Mass", &RigidBodyConfiguration::m_computeMass)
@@ -57,7 +57,6 @@ namespace AzPhysics
bool m_startAsleep = false;
bool m_interpolateMotion = false;
bool m_gravityEnabled = true;
bool m_simulated = true;
bool m_kinematic = false;
bool m_ccdEnabled = false; //!< Whether continuous collision detection is enabled.
float m_ccdMinAdvanceCoefficient = 0.15f; //!< Coefficient affecting how granularly time is subdivided in CCD.
@@ -88,13 +88,13 @@ namespace AzPhysics
//! Remove a simulated body from the Scene.z
//! @param sceneHandle A handle to the scene to remove the requested simulated body.
//! @param bodyHandle A handle to the simulated body being removed.
virtual void RemoveSimulatedBody(SceneHandle sceneHandle, SimulatedBodyHandle bodyHandle) = 0;
//! @param bodyHandle A handle to the simulated body being removed. This will be set to AzPhysics::InvalidSimulatedBodyHandle as they're no longer valid.
virtual void RemoveSimulatedBody(SceneHandle sceneHandle, SimulatedBodyHandle& bodyHandle) = 0;
//! Remove a list of simulated bodies from the Scene.
//! @param sceneHandle A handle to the scene to remove the simulated bodies from.
//! @param bodyHandles A list of simulated body handles to be removed.
virtual void RemoveSimulatedBodies(SceneHandle sceneHandle, const SimulatedBodyHandleList& bodyHandles) = 0;
//! @param bodyHandles A list of simulated body handles to be removed. All handles will be set to AzPhysics::InvalidSimulatedBodyHandle as they're no longer valid.
virtual void RemoveSimulatedBodies(SceneHandle sceneHandle, SimulatedBodyHandleList& bodyHandles) = 0;
//! Enable / Disable simulation of the requested body. By default all bodies added are enabled.
//! Disabling simulation the body will no longer be affected by any forces, collisions, or found with scene queries.
@@ -286,12 +286,12 @@ namespace AzPhysics
virtual SimulatedBodyList GetSimulatedBodiesFromHandle(const SimulatedBodyHandleList& bodyHandles) = 0;
//! Remove a simulated body from the Scene.
//! @param bodyHandle A handle to the simulated body being removed.
virtual void RemoveSimulatedBody(SimulatedBodyHandle bodyHandle) = 0;
//! @param bodyHandle A handle to the simulated body being removed. This will be set to AzPhysics::InvalidSimulatedBodyHandle as they're no longer valid.
virtual void RemoveSimulatedBody(SimulatedBodyHandle& bodyHandle) = 0;
//! Remove a list of simulated bodies from the Scene.
//! @param bodyHandles A list of simulated body handles to be removed.
virtual void RemoveSimulatedBodies(const SimulatedBodyHandleList& bodyHandles) = 0;
//! @param bodyHandles A list of simulated body handles to be removed. All handles will be set to AzPhysics::InvalidSimulatedBodyHandle as they're no longer valid.
virtual void RemoveSimulatedBodies(SimulatedBodyHandleList& bodyHandles) = 0;
//! Enable / Disable simulation of the requested body. By default all bodies added are enabled.
//! Disabling simulation the body will no longer be affected by any forces, collisions, or found with scene queries.
@@ -62,7 +62,7 @@ namespace AzPhysics
virtual void SetLinearVelocity(const AZ::Vector3& velocity) = 0;
virtual AZ::Vector3 GetAngularVelocity() const = 0;
virtual void SetAngularVelocity(const AZ::Vector3& angularVelocity) = 0;
virtual AZ::Vector3 GetLinearVelocityAtWorldPoint(const AZ::Vector3& worldPoint) = 0;
virtual AZ::Vector3 GetLinearVelocityAtWorldPoint(const AZ::Vector3& worldPoint) const = 0;
virtual void ApplyLinearImpulse(const AZ::Vector3& impulse) = 0;
virtual void ApplyLinearImpulseAtWorldPoint(const AZ::Vector3& impulse, const AZ::Vector3& worldPoint) = 0;
virtual void ApplyAngularImpulse(const AZ::Vector3& angularImpulse) = 0;
@@ -13,6 +13,7 @@
#include <AzCore/Component/EntityId.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Vector3.h>
namespace Physics
@@ -26,12 +26,9 @@
#include <AzCore/std/string/conversions.h>
#include <AzFramework/Script/ScriptComponent.h>
#include <AzFramework/Script/ScriptNetBindings.h>
#include <AzFramework/Network/NetworkContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <GridMate/Replica/ReplicaChunk.h>
#include <AzFramework/IO/LocalFileIO.h>
@@ -429,83 +426,60 @@ namespace AzFramework
{
LSV_BEGIN(lua, 1);
// calling format __index(table,key)
ScriptNetBindingTable* netBindingTable = reinterpret_cast<ScriptNetBindingTable*>(lua_touserdata(lua, lua_upvalueindex(1)));
AZ::ScriptContext::FromNativeContext(lua)->Error(AZ::ScriptContext::ErrorType::Warning, true,
"Property %s not found in entity table. Please push this property to your slice to avoid decrease in performance.", lua_tostring(lua, -1));
int lookupKey = lua_gettop(lua);
bool readValue = false;
if (netBindingTable != nullptr)
int lookupTable = lookupKey - 1;
// This is a slow function and it's made slow so we don't cache any extra data.
// This is done because this function will be called only the exported components
// and script are not in sync and we added new properties.
lua_getmetatable(lua, -2); // get the metatable which will be the top property table
int entityProperties = lua_gettop(lua);
if (lua_getmetatable(lua, -1) == 0) // get the metatable of the property which will be the original table
{
AZ_Error("ScriptComponent",netBindingTable->GetScriptContext() != nullptr,"ScriptNetBindingTable is missing ScriptContext.");
AZ_Error("ScriptComponent",netBindingTable->GetScriptContext() == nullptr || netBindingTable->GetScriptContext()->NativeContext() == lua,"Trying to use a NetBindingTable in wrong lua context");
AZ::ScriptContext* scriptContext = netBindingTable->GetScriptContext();
if (scriptContext)
{
AZ::ScriptDataContext stackContext;
scriptContext->ReadStack(stackContext);
readValue = netBindingTable->InspectTableValue(stackContext);
}
// we are looking at top level properties
lua_pushvalue(lua, -2); // copy the key
lua_rawget(lua, -2); // read the value
}
if (!readValue)
else
{
AZ::ScriptContext::FromNativeContext(lua)->Error(AZ::ScriptContext::ErrorType::Warning, true,
"Property %s not found in entity table. Please push this property to your slice to avoid decrease in performance.", lua_tostring(lua, -1));
int lookupKey = lua_gettop(lua);
int lookupTable = lookupKey - 1;
// This is a slow function and it's made slow so we don't cache any extra data.
// This is done because this function will be called only the exported components
// and script are not in sync and we added new properties.
lua_getmetatable(lua, -2); // get the metatable which will be the top property table
int entityProperties = lua_gettop(lua);
if (lua_getmetatable(lua, -1) == 0) // get the metatable of the property which will be the original table
// we are looking into the sub table, so do a slow traversal
int scriptProperties = lua_gettop(lua);
if (!Properties__IndexFindSubtable(lua, lookupTable, entityProperties, scriptProperties))
{
// we are looking at top level properties
lua_pushvalue(lua, -2); // copy the key
lua_rawget(lua, -2); // read the value
lua_pushnil(lua);
return 1; // we did not find the table
}
else
{
// we are looking into the sub table, so do a slow traversal
int scriptProperties = lua_gettop(lua);
if (!Properties__IndexFindSubtable(lua, lookupTable, entityProperties, scriptProperties))
{
lua_pushnil(lua);
return 1; // we did not find the table
}
else
{
lua_pushvalue(lua, lookupKey);
lua_rawget(lua, -2);
}
}
if (lua_istable(lua, -1))
{
// if we are here the target table is on the top if the stack
lua_pushstring(lua, ScriptComponent::DefaultFieldName);
lua_pushvalue(lua, lookupKey);
lua_rawget(lua, -2);
if (lua_isnil(lua, -1))
{
// parent table is a group, pop the value and return the table
lua_pop(lua, 1);
}
}
// Duplicate the value, so once the storage is done its on top of the stack, and returned
lua_pushvalue(lua, -1);
// Push key, and then move it below the value
lua_pushvalue(lua, lookupKey);
lua_insert(lua, -2);
// Cache the value so that subsequent accesses to this property don't result in warnings
lua_rawset(lua, lookupTable);
}
if (lua_istable(lua, -1))
{
// if we are here the target table is on the top if the stack
lua_pushstring(lua, ScriptComponent::DefaultFieldName);
lua_rawget(lua, -2);
if (lua_isnil(lua, -1))
{
// parent table is a group, pop the value and return the table
lua_pop(lua, 1);
}
}
// Duplicate the value, so once the storage is done its on top of the stack, and returned
lua_pushvalue(lua, -1);
// Push key, and then move it below the value
lua_pushvalue(lua, lookupKey);
lua_insert(lua, -2);
// Cache the value so that subsequent accesses to this property don't result in warnings
lua_rawset(lua, lookupTable);
return 1;
}
//=========================================================================
@@ -515,30 +489,7 @@ namespace AzFramework
{
LSV_BEGIN_VARIABLE(lua);
// calling format __newindex(table,key,value)
ScriptNetBindingTable* netBindingTable = reinterpret_cast<ScriptNetBindingTable*>(lua_touserdata(lua, lua_upvalueindex(1)));
if (netBindingTable != nullptr)
{
AZ_Error("ScriptContext",netBindingTable->GetScriptContext() != nullptr,"ScriptNetBindingTable is missing ScriptContext.");
AZ_Error("ScriptContext",netBindingTable->GetScriptContext() == nullptr || netBindingTable->GetScriptContext()->NativeContext() == lua,"Trying to use a NetBindingTable in wrong lua context");
AZ::ScriptContext* scriptContext = netBindingTable->GetScriptContext();
if (scriptContext)
{
AZ::ScriptDataContext stackContext;
scriptContext->ReadStack(stackContext);
const bool assignedValue = netBindingTable->AssignTableValue(stackContext);
if (assignedValue)
{
LSV_END_VARIABLE(0);
return 0;
}
}
}
// If we didn't assign the value above, we want
// to raw set the value to avoid coming back in here.
// We want to raw set the value to avoid coming back in here.
lua_rawset(lua, 1);
LSV_END_VARIABLE(-2);
return 0;
@@ -553,7 +504,6 @@ namespace AzFramework
// [8/9/2013]
//=========================================================================
const char* ScriptComponent::NetRPCFieldName = "NetRPCs";
const char* ScriptComponent::DefaultFieldName = "default";
ScriptComponent::ScriptComponent()
@@ -561,7 +511,6 @@ namespace AzFramework
, m_contextId(AZ::ScriptContextIds::DefaultScriptContextId)
, m_script(AZ::Data::AssetLoadBehavior::PreLoad)
, m_table(LUA_NOREF)
, m_netBindingTable(nullptr)
{
m_properties.m_name = "Properties";
}
@@ -573,8 +522,6 @@ namespace AzFramework
ScriptComponent::~ScriptComponent()
{
m_properties.Clear();
delete m_netBindingTable;
}
//=========================================================================
@@ -604,11 +551,6 @@ namespace AzFramework
return m_properties.GetProperty(propertyName);
}
const AZ::ScriptProperty* ScriptComponent::GetNetworkedScriptProperty(const char* propertyName) const
{
return m_netBindingTable->FindScriptProperty(propertyName);
}
void ScriptComponent::Init()
{
// Grab the script context
@@ -622,11 +564,6 @@ namespace AzFramework
//=========================================================================
void ScriptComponent::Activate()
{
if (m_isSyncEnabled && m_netBindingTable == nullptr)
{
m_netBindingTable = aznew ScriptNetBindingTable();
}
// if we have valid asset listen for script asset events, like reload
if (m_script.GetId().IsValid())
{
@@ -681,43 +618,6 @@ namespace AzFramework
LoadScript();
}
//=========================================================================
// ScriptComponent::GetNetworkBinding
//=========================================================================
GridMate::ReplicaChunkPtr ScriptComponent::GetNetworkBinding()
{
if (m_netBindingTable == nullptr)
{
m_netBindingTable = aznew ScriptNetBindingTable();
}
return m_netBindingTable->GetNetworkBinding();
}
//=========================================================================
// ScriptComponent::SetNetworkBinding
//=========================================================================
void ScriptComponent::SetNetworkBinding(GridMate::ReplicaChunkPtr chunk)
{
if (m_netBindingTable == nullptr)
{
m_netBindingTable = aznew ScriptNetBindingTable();
}
m_netBindingTable->SetNetworkBinding(chunk);
}
//=========================================================================
// ScriptComponent::UnbindFromNetwork
//=========================================================================
void ScriptComponent::UnbindFromNetwork()
{
if (m_netBindingTable)
{
m_netBindingTable->UnbindFromNetwork();
}
}
//=========================================================================
// LoadScript
//=========================================================================
@@ -741,11 +641,6 @@ namespace AzFramework
AZ_PROFILE_SCOPE_DYNAMIC(AZ::Debug::ProfileCategory::Script, "Unload: %s", m_script.GetHint().c_str());
DestroyEntityTable();
if (m_netBindingTable)
{
m_netBindingTable->Unload();
}
}
//=========================================================================
@@ -798,12 +693,10 @@ namespace AzFramework
// set the __index so we can read values in case we change the script
// after we export the component
lua_pushliteral(lua, "__index");
lua_pushlightuserdata(lua, m_netBindingTable);
lua_pushcclosure(lua, &Internal::Properties__Index, 1);
lua_rawset(lua, -3);
lua_pushliteral(lua, "__newindex");
lua_pushlightuserdata(lua, m_netBindingTable);
lua_pushcclosure(lua, &Internal::Properties__NewIndex, 1);
lua_rawset(lua, -3);
}
@@ -835,8 +728,7 @@ namespace AzFramework
{
const char* tableName = lua_tolstring(lua, -2, nullptr);
if (strncmp(tableName, "__", 2) == 0 || // skip metatables
strcmp(tableName, propertyTableName) == 0 || // Skip the Properties table
strcmp(tableName, ScriptComponent::NetRPCFieldName) == 0) // Want to skip the RPC table as well
strcmp(tableName, propertyTableName) == 0) // Skip the Properties table
{
break;
}
@@ -904,13 +796,10 @@ namespace AzFramework
}
lua_createtable(lua, 0, 1); // Create entity table;
int entityStackIndex = lua_gettop(lua);
[[maybe_unused]] int entityStackIndex = lua_gettop(lua);
// Stack: ScriptRootTable PropertiesTable EntityTable
// Create our network binding.
CreateNetworkBindingTable(baseStackIndex, entityStackIndex);
if (basePropertyTable > -1) // if property table exists
{
CreatePropertyGroup(m_properties, basePropertyTable, lua_gettop(lua), basePropertyTable, true);
@@ -932,11 +821,6 @@ namespace AzFramework
// Keep the entity table in the registry
m_table = luaL_ref(lua, LUA_REGISTRYINDEX);
if (m_netBindingTable)
{
m_netBindingTable->FinalizeNetworkTable(m_context, m_table);
}
// call OnActivate
lua_pushliteral(lua, "OnActivate");
lua_rawget(lua, baseStackIndex); // ScriptTable[OnActivate]
@@ -993,18 +877,6 @@ namespace AzFramework
}
}
//=========================================================================
// CreateNetworkBindingTable
// [6/27/2016]
//=========================================================================
void ScriptComponent::CreateNetworkBindingTable(int baseStackIndex, int entityStackIndex)
{
if (m_netBindingTable)
{
m_netBindingTable->CreateNetworkBindingTable(m_context, baseStackIndex, entityStackIndex);
}
}
//=========================================================================
// CreatePropertyGroup
// [3/3/2014]
@@ -1028,12 +900,10 @@ namespace AzFramework
// Ensure that this instance of Properties table has the proper __index and __newIndex metamethods.
lua_newtable(lua); // This new table will become the Properties instance metatable. Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {}
lua_pushliteral(lua, "__index"); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index
lua_pushlightuserdata(lua, m_netBindingTable); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index m_netBinding
lua_pushcclosure(lua, &Internal::Properties__Index, 1); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index function
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {__index=Internal::Properties__Index}
lua_pushliteral(lua, "__newindex");
lua_pushlightuserdata(lua, m_netBindingTable);
lua_pushcclosure(lua, &Internal::Properties__NewIndex, 1);
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {__index=Internal::Properties__Index __newindex=Internal::Properties__NewIndex}
lua_setmetatable(lua, -2); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {Meta{__index=Internal::Properties__Index __newindex=Internal::Properties__NewIndex} }
@@ -1050,55 +920,6 @@ namespace AzFramework
{
AZ::ScriptProperty* prop = group.m_properties[i];
if (m_netBindingTable != nullptr)
{
lua_pushlstring(lua, prop->m_name.c_str(), prop->m_name.length());
lua_rawget(lua, propertyGroupTableIndex);
// Stack: ... SomePropertyInThePropertiesTable. This may be any basic lua type (number, string, table etc)
if (lua_istable(lua, -1))
{
bool isNetworkedProperty = false;
AZ::ScriptDataContext stackContext;
// If we find a table value. We want to inspect it for information.
if (m_context->ReadStack(stackContext))
{
// check if the current property, which is a table, has a sub-table called "netSynched"
lua_pushliteral(lua, "netSynched"); // Stack: ... SomePropertyInThePropertiesTable netSynched
lua_rawget(lua, -2); // Stack: ... SomePropertyInThePropertiesTable NetSynchedSubTable/nil
if (stackContext.IsTable(-1))
{
AZ::ScriptDataContext networkTableContext;
if (stackContext.InspectTable(-1, networkTableContext)) // Stack: ... SomePropertyInThePropertiesTable NetSynchedSubTable NetSynchedSubTable nil nil
{
// RegisterDataSet will make sure our __NewIndex function callback will be triggered whenever modifying netSynched Properties.
//isNetworkedProperty = true;
isNetworkedProperty = m_netBindingTable->RegisterDataSet(networkTableContext, prop);
}
}
// Network binding table
lua_pop(lua, 1); // Stack: ... SomePropertyInThePropertiesTable
}
// Pop this PropertiesTable's property
lua_pop(lua, 1);
// If the property is networked, we don't want to copy it over into the table.
if (isNetworkedProperty)
{
continue;
}
}
else
{
// Remove the value we just pushed onto the stack
lua_pop(lua, 1);
}
}
lua_pushlstring(lua, prop->m_name.c_str(), prop->m_name.length());
if (prop->Write(*m_context))
{
@@ -1157,8 +978,8 @@ namespace AzFramework
return true;
};
serializeContext->Class<ScriptComponent, AZ::Component, NetBindable>()
->Version(3, converter)
serializeContext->Class<ScriptComponent, AZ::Component>()
->Version(4, converter)
->Field("ContextID", &ScriptComponent::m_contextId)
->Field("Properties", &ScriptComponent::m_properties)
->Field("Script", &ScriptComponent::m_script)
@@ -1174,8 +995,6 @@ namespace AzFramework
AZ::ScriptProperties::Reflect(reflection);
}
}
ScriptNetBindingTable::Reflect(reflection);
}
//=========================================================================
@@ -20,8 +20,6 @@
#include <AzCore/std/string/string.h>
#include <AzCore/std/smart_ptr/intrusive_ptr.h>
#include <AzFramework/Network/NetBindable.h>
namespace AZ
{
class ScriptProperty;
@@ -37,8 +35,6 @@ namespace AzToolsFramework
namespace AzFramework
{
class ScriptNetBindingTable;
struct ScriptCompileRequest;
using WriteFunction = AZStd::function< AZ::Outcome<void, AZStd::string>(const ScriptCompileRequest&, AZ::IO::GenericStream& in, AZ::IO::GenericStream& out) >;
@@ -92,15 +88,13 @@ namespace AzFramework
class ScriptComponent
: public AZ::Component
, private AZ::Data::AssetBus::Handler
, public AzFramework::NetBindable
{
friend class AzToolsFramework::Components::ScriptEditorComponent;
public:
static const char* NetRPCFieldName;
static const char* DefaultFieldName;
AZ_COMPONENT(AzFramework::ScriptComponent, "{8D1BC97E-C55D-4D34-A460-E63C57CD0D4B}", NetBindable);
AZ_COMPONENT(AzFramework::ScriptComponent, "{8D1BC97E-C55D-4D34-A460-E63C57CD0D4B}", AZ::Component);
/// \red ComponentDescriptor::Reflect
static void Reflect(AZ::ReflectContext* reflection);
@@ -116,7 +110,6 @@ namespace AzFramework
// Methods used for unit tests
AZ::ScriptProperty* GetScriptProperty(const char* propertyName);
const AZ::ScriptProperty* GetNetworkedScriptProperty(const char* propertyName) const;
protected:
ScriptComponent(const ScriptComponent&) = delete;
@@ -133,13 +126,6 @@ namespace AzFramework
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// NetBindable
GridMate::ReplicaChunkPtr GetNetworkBinding() override;
void SetNetworkBinding(GridMate::ReplicaChunkPtr chunk) override;
void UnbindFromNetwork() override;
//////////////////////////////////////////////////////////////////////////
/// Load script (unless already by other instances) and creates the script instance into the VM
void LoadScript();
/// Removes the script instance and unloads the script (unless needed by other instances)
@@ -152,8 +138,6 @@ namespace AzFramework
void CreateEntityTable();
void DestroyEntityTable();
void CreateNetworkBindingTable(int baseStackIndex, int entityStackIndex);
void CreatePropertyGroup(const ScriptPropertyGroup& group, int propertyGroupTableIndex, int parentIndex, int metatableIndex, bool isRoot);
AZ::ScriptContext* m_context; ///< Context in which the script will be running
@@ -161,7 +145,6 @@ namespace AzFramework
AZ::Data::Asset<AZ::ScriptAsset> m_script; ///< Reference to the script asset used for this component.
int m_table; ///< Cached table index
ScriptPropertyGroup m_properties; ///< List with all properties that were tweaked in the editor and should override values in the m_sourceScriptName class inside m_script.
ScriptNetBindingTable* m_netBindingTable; ///< Table that will hold our networked script values, and manage callbacks
};
} // namespace AZ
@@ -1,573 +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.
*
*/
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Script/ScriptProperty.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/string/string.h>
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Serialize/DataMarshal.h>
#include <GridMate/Serialize/UuidMarshal.h>
#include <GridMate/Serialize/ContainerMarshal.h>
#include <AzFramework/Script/ScriptNetBindings.h>
#include <AzFramework/Network/DynamicSerializableFieldMarshaler.h>
#include <AzFramework/Network/EntityIdMarshaler.h>
#include "AzFramework/Script/ScriptMarshal.h"
namespace AzFramework
{
////////////////////////////
// ScriptPropertyMarshaler
////////////////////////////
template<class T>
bool UnmarshalGenericType(AZ::DynamicSerializableField& serializableField, GridMate::ReadBuffer& rb)
{
bool valueChanged = true;
GridMate::Marshaler<AZ::DynamicSerializableField> serializableFieldMarshaler;
// Store the old value, to compare with the unmarshaled value, to signal
T oldValue = (*serializableField.Get<T>());
serializableFieldMarshaler.Unmarshal(serializableField,rb);
// If our type hasn't changed, compare the values.
if (serializableField.m_typeId == T::TYPEINFO_Uuid())
{
valueChanged = !(oldValue == (*serializableField.Get<T>()));
}
return valueChanged;
}
class ScriptPropertyTableMarshalerHelper
{
public:
template<typename T>
static void MarshalScriptPropertyGenericMap(const ScriptPropertyMarshaler& scriptPropertyMarshaler, GridMate::WriteBuffer& wb, const AZ::ScriptPropertyTable* scriptPropertyTable)
{
GridMate::Marshaler<AZ::u32> sizeMarshaler;
auto mapIter = scriptPropertyTable->m_genericMapping.find(T::TYPEINFO_Uuid());
if (mapIter != scriptPropertyTable->m_genericMapping.end())
{
AZ::ScriptPropertyGenericClassMapImpl<T>* genericClassKeyMap = static_cast<AZ::ScriptPropertyGenericClassMapImpl<T>*>(mapIter->second);
auto& valueMap = genericClassKeyMap->GetPairMapping();
// We will write out all of our keys. Since it is easier to write out nil values for the properties.
sizeMarshaler.Marshal(wb,static_cast<AZ::u32>(valueMap.size()));
GridMate::Marshaler<T> keyMarshaler;
for (auto& mapPair : valueMap)
{
keyMarshaler.Marshal(wb,mapPair.first);
scriptPropertyMarshaler.Marshal(wb,mapPair.second.m_valueProperty);
}
}
else
{
sizeMarshaler.Marshal(wb,0);
}
}
template<typename T>
static bool UnmarshalScriptPropertyGenericMap(const ScriptPropertyMarshaler& scriptPropertyMarshaler, AZ::ScriptPropertyTable* scriptPropertyTable, GridMate::ReadBuffer& rb)
{
bool valueChanged = false;
AZ::SerializeContext* useContext = nullptr;
EBUS_EVENT_RESULT(useContext, AZ::ComponentApplicationBus, GetSerializeContext);
if (useContext)
{
const AZ::SerializeContext::ClassData* classData = useContext->FindClassData(T::TYPEINFO_Uuid());
if (classData && classData->m_factory)
{
auto mapIter = scriptPropertyTable->m_genericMapping.find(T::TYPEINFO_Uuid());
if (mapIter != scriptPropertyTable->m_genericMapping.end())
{
// This whole thing is an in-place map update.
// to try to minimize the number of allocations. We try to re-use objects as much as possible.
//
// Two phase approach: Step one, update all of the existing properties, while keeping track of all of the used keys.
// Step two, go through and delete any unupdated keys from the mapping.
AZ::ScriptPropertyGenericClassMapImpl<T>* genericClassKeyMap = static_cast<AZ::ScriptPropertyGenericClassMapImpl<T>*>(mapIter->second);
AZStd::unordered_set<T> newKeys;
GridMate::Marshaler<AZ::u32> sizeMarshaler;
AZ::u32 mapSize;
sizeMarshaler.Unmarshal(mapSize,rb);
auto& valueMap = genericClassKeyMap->GetPairMapping();
GridMate::Marshaler<T> keyMarshaler;
for (unsigned int i=0; i < mapSize; ++i)
{
T propertyKey;
keyMarshaler.Unmarshal(propertyKey,rb);
newKeys.insert(propertyKey);
auto valueIter = valueMap.find(propertyKey);
if (valueIter != valueMap.end())
{
if (scriptPropertyMarshaler.UnmarshalToPointer(valueIter->second.m_valueProperty,rb))
{
valueChanged = true;
}
}
else
{
valueChanged = true;
AZ::ScriptProperty* newValueProperty = nullptr;
scriptPropertyMarshaler.UnmarshalToPointer(newValueProperty,rb);
AZ::ScriptPropertyGenericClassMap::MapValuePair newPair;
newPair.m_valueProperty = newValueProperty;
T* serializableData = nullptr;
serializableData = static_cast<T*>(classData->m_factory->Create("ScriptProperty"));
(*serializableData) = propertyKey;
AZ::ScriptPropertyGenericClass* genericPropertyClass = aznew AZ::ScriptPropertyGenericClass();
genericPropertyClass->Set<T>(serializableData);
newPair.m_keyProperty = genericPropertyClass;
valueMap.emplace(propertyKey,newPair);
}
}
// Delete all of the unused keyes from the map
auto valueIter = valueMap.begin();
while (valueIter != valueMap.end())
{
if (newKeys.find(valueIter->first) == newKeys.end())
{
valueChanged = true;
valueIter->second.Destroy();
valueIter = valueMap.erase(valueIter);
}
else
{
++valueIter;
}
}
}
}
}
return valueChanged;
}
};
void ScriptPropertyMarshaler::Marshal(GridMate::WriteBuffer& wb, AZ::ScriptProperty*const& property) const
{
GridMate::Marshaler<AZ::Uuid> typeMarshaler;
GridMate::Marshaler<AZ::u64> idMarshaler;
GridMate::Marshaler<AZStd::string> nameMarshaler;
if (property == nullptr)
{
// Write out a nil property if we have a nullptr property
nameMarshaler.Marshal(wb,"");
idMarshaler.Marshal(wb,0);
typeMarshaler.Marshal(wb,AZ::ScriptPropertyNil::RTTI_Type());
return;
}
// Common points:
// Always going to marshal the uuid of the type(or something similar)
// so we know what type we have on the other side.
//
// Next need to pass along the name field.
const AZ::Uuid& typeId = azrtti_typeid(property);
nameMarshaler.Marshal(wb,property->m_name);
idMarshaler.Marshal(wb,property->m_id);
// Method 1:
// - Allow each ScriptProperty to marshal itself.
// - Currently unavailable since the ScriptProperties live in AZCore
// and the WriteBuffer is in GridMate.
// cont.Marshal(wb);
// Method 2:
// - Process all of our known marshallable types and use the appropriate marshaler
if (typeId == AZ::ScriptPropertyBoolean::RTTI_Type())
{
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<bool> boolMarshaler;
boolMarshaler.Marshal(wb,static_cast<const AZ::ScriptPropertyBoolean*>(property)->m_value);
}
else if (typeId == AZ::ScriptPropertyNumber::RTTI_Type())
{
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<double> doubleMarshaler;
doubleMarshaler.Marshal(wb,static_cast<const AZ::ScriptPropertyNumber*>(property)->m_value);
}
else if (typeId == AZ::ScriptPropertyString::RTTI_Type())
{
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<AZStd::string> stringMarshaler;
stringMarshaler.Marshal(wb,static_cast<const AZ::ScriptPropertyString*>(property)->m_value);
}
else if (typeId == AZ::ScriptPropertyGenericClass::RTTI_Type())
{
const AZ::DynamicSerializableField& serializableField = static_cast<const AZ::ScriptPropertyGenericClass*>(property)->GetSerializableField();
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<AZ::DynamicSerializableField> serializableFieldMarshaler;
serializableFieldMarshaler.Marshal(wb,serializableField);
}
else if (typeId == AZ::ScriptPropertyTable::TYPEINFO_Uuid())
{
const AZ::ScriptPropertyTable* scriptPropertyTable = static_cast<const AZ::ScriptPropertyTable*>(property);
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<AZ::u32> mapSizeMarshaler;
mapSizeMarshaler.Marshal(wb,static_cast<AZ::u32>(scriptPropertyTable->m_indexMapping.size()));
GridMate::Marshaler<int> indexMarshaler;
// Currently only support integers as keys inside of the table.
for (auto& mapPair : scriptPropertyTable->m_indexMapping)
{
indexMarshaler.Marshal(wb,mapPair.first);
this->Marshal(wb,mapPair.second);
}
mapSizeMarshaler.Marshal(wb, static_cast<AZ::u32>(scriptPropertyTable->m_keyMapping.size()));
GridMate::Marshaler<AZ::u32> hashMarshaler;
for (auto& mapPair : scriptPropertyTable->m_keyMapping)
{
// For hashed values. The name of the script property is the same as the hash it should be using.
// We still synchronize the Crc so we can unmarshal in place on the other side.
hashMarshaler.Marshal(wb,mapPair.first);
Marshal(wb,mapPair.second);
}
// EntityId's
ScriptPropertyTableMarshalerHelper::MarshalScriptPropertyGenericMap<AZ::EntityId>((*this), wb, scriptPropertyTable);
}
else
{
typeMarshaler.Marshal(wb,AZ::ScriptPropertyNil::RTTI_Type());
}
}
bool ScriptPropertyMarshaler::UnmarshalToPointer(AZ::ScriptProperty*& target, GridMate::ReadBuffer& rb) const
{
bool typeChanged = false;
AZ::Uuid typeId;
AZ::u64 id;
AZStd::string name;
GridMate::Marshaler<AZ::Uuid> typeMarshaler;
GridMate::Marshaler<AZ::u64> idMarshaler;
GridMate::Marshaler<AZStd::string> nameMarshaler;
nameMarshaler.Unmarshal(name,rb);
idMarshaler.Unmarshal(id,rb);
typeMarshaler.Unmarshal(typeId,rb);
if (target == nullptr || typeId != azrtti_typeid(target))
{
typeChanged = true;
AZ::ScriptProperty* actualScriptProperty = nullptr;
if (typeId == AZ::ScriptPropertyBoolean::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyBoolean();
}
else if (typeId == AZ::ScriptPropertyNumber::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyNumber();
}
else if (typeId == AZ::ScriptPropertyString::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyString();
}
else if (typeId == AZ::ScriptPropertyGenericClass::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyGenericClass();
}
else if (typeId == AZ::ScriptPropertyTable::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyTable();
}
else
{
actualScriptProperty = aznew AZ::ScriptPropertyNil();
}
actualScriptProperty->m_name = name;
delete target;
target = actualScriptProperty;
}
// Update our ID
target->m_id = id;
// Method 1:
// - Allow each ScriptProperty to unmarshal itself
// - Currently unavailable since the ScriptProperties live in AZCore
// and the WriteBuffer is in GridMate
// actualScriptProperty->Unmarshal(rb);
//
// Method 2:
// - Process all of our known marshallable types and use the appropriate marshaler
bool valueChanged = false;
if (typeId == AZ::ScriptPropertyBoolean::RTTI_Type())
{
AZ::ScriptPropertyBoolean* booleanProperty = static_cast<AZ::ScriptPropertyBoolean*>(target);
bool oldValue = booleanProperty->m_value;
GridMate::Marshaler<bool> boolMarshaler;
boolMarshaler.Unmarshal(booleanProperty->m_value,rb);
valueChanged = !(oldValue == booleanProperty->m_value);
}
else if (typeId == AZ::ScriptPropertyString::RTTI_Type())
{
AZ::ScriptPropertyString* stringProperty = static_cast<AZ::ScriptPropertyString*>(target);
AZStd::string oldValue = stringProperty->m_value;
GridMate::Marshaler<AZStd::string> stringMarshaler;
stringMarshaler.Unmarshal(stringProperty->m_value,rb);
valueChanged = !(oldValue == stringProperty->m_value);
}
else if (typeId == AZ::ScriptPropertyNumber::RTTI_Type())
{
AZ::ScriptPropertyNumber* numberProperty = static_cast<AZ::ScriptPropertyNumber*>(target);
double oldValue = numberProperty->m_value;
GridMate::Marshaler<double> numberMarshaler;
numberMarshaler.Unmarshal(numberProperty->m_value,rb);
valueChanged = !(oldValue == numberProperty->m_value);
}
else if (typeId == AZ::ScriptPropertyGenericClass::RTTI_Type())
{
AZ::ScriptPropertyGenericClass* genericProperty = static_cast<AZ::ScriptPropertyGenericClass*>(target);
AZ::DynamicSerializableField& serializableField = genericProperty->m_value;
AZ::DynamicSerializableField oldField;
oldField.CopyDataFrom(serializableField);
GridMate::Marshaler<AZ::DynamicSerializableField> serializableFieldMarshaler;
serializableFieldMarshaler.Unmarshal(serializableField,rb);
// If our type hasn't changed, compare the values.
valueChanged = !oldField.IsEqualTo(serializableField);
}
else if (typeId == AZ::ScriptPropertyTable::RTTI_Type())
{
AZ::ScriptPropertyTable* scriptPropertyTable = static_cast<AZ::ScriptPropertyTable*>(target);
GridMate::Marshaler<AZ::u32> mapSizeMarshaler;
// Unmarshal all of the indexes properties
{
AZ::u32 mapSize = 0;
mapSizeMarshaler.Unmarshal(mapSize, rb);
AZStd::unordered_set<int> newIndexes;
GridMate::Marshaler<int> indexMarshaler;
for (AZ::u32 i=0; i < mapSize; ++i)
{
int index = 0;
indexMarshaler.Unmarshal(index,rb);
auto mapIter = scriptPropertyTable->m_indexMapping.find(index);
if (mapIter != scriptPropertyTable->m_indexMapping.end())
{
if (UnmarshalToPointer(mapIter->second,rb))
{
valueChanged = true;
}
}
else
{
valueChanged = true;
AZ::ScriptProperty* scriptProperty = nullptr;
UnmarshalToPointer(scriptProperty,rb);
auto insertResult = scriptPropertyTable->m_indexMapping.emplace(index,scriptProperty);
mapIter = insertResult.first;
}
if (mapIter->second == nullptr || azrtti_istypeof<AZ::ScriptPropertyNil>(mapIter->second))
{
valueChanged = true;
delete mapIter->second;
scriptPropertyTable->m_indexMapping.erase(mapIter);
}
else
{
newIndexes.insert(index);
}
}
auto mapIter = scriptPropertyTable->m_indexMapping.begin();
while (mapIter != scriptPropertyTable->m_indexMapping.end())
{
if (newIndexes.find(mapIter->first) == newIndexes.end())
{
valueChanged = true;
delete mapIter->second;
mapIter = scriptPropertyTable->m_indexMapping.erase(mapIter);
}
else
{
++mapIter;
}
}
}
// Unmarshal all of the hashed values
{
AZ::u32 mapSize = 0;
mapSizeMarshaler.Unmarshal(mapSize, rb);
AZStd::unordered_set<AZ::u32> newHashes;
GridMate::Marshaler<AZ::u32> hashMarshaler;
for (AZ::u32 i=0; i < mapSize; ++i)
{
AZ::u32 newHash;
hashMarshaler.Unmarshal(newHash, rb);
auto mapIter = scriptPropertyTable->m_keyMapping.find(newHash);
if (mapIter != scriptPropertyTable->m_keyMapping.end())
{
if (UnmarshalToPointer(mapIter->second,rb))
{
valueChanged = true;
}
}
else
{
valueChanged = true;
AZ::ScriptProperty* scriptProperty = nullptr;
UnmarshalToPointer(scriptProperty,rb);
auto emplaceResult = scriptPropertyTable->m_keyMapping.emplace(newHash,scriptProperty);
mapIter = emplaceResult.first;
}
if (mapIter->second == nullptr || azrtti_istypeof<AZ::ScriptPropertyNil>(mapIter->second))
{
valueChanged = true;
delete mapIter->second;
scriptPropertyTable->m_keyMapping.erase(mapIter);
}
else
{
newHashes.insert(newHash);
}
}
auto mapIter = scriptPropertyTable->m_keyMapping.begin();
while (mapIter != scriptPropertyTable->m_keyMapping.end())
{
if (newHashes.find(mapIter->first) == newHashes.end())
{
valueChanged = true;
delete mapIter->second;
mapIter = scriptPropertyTable->m_keyMapping.erase(mapIter);
}
else
{
++mapIter;
}
}
}
// Unmarshal all of the generic properties
// EntityId's
if (ScriptPropertyTableMarshalerHelper::UnmarshalScriptPropertyGenericMap<AZ::EntityId>((*this), scriptPropertyTable, rb))
{
valueChanged = true;
}
}
return typeChanged || valueChanged;
}
////////////////////////////
// ScriptPropertyThrottler
////////////////////////////
ScriptPropertyThrottler::ScriptPropertyThrottler()
: m_isDirty(true)
{
}
void ScriptPropertyThrottler::SignalDirty()
{
m_isDirty = true;
}
bool ScriptPropertyThrottler::WithinThreshold(AZ::ScriptProperty* newValue) const
{
return newValue == nullptr || !m_isDirty;
}
void ScriptPropertyThrottler::UpdateBaseline(AZ::ScriptProperty* baseline)
{
(void)baseline;
m_isDirty = false;
}
}
@@ -1,94 +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.
*
*/
#ifndef AZFRAMEWORK_SCRIPT_SCRIPTMARSHAL_H
#define AZFRAMEWORK_SCRIPT_SCRIPTMARSHAL_H
#include <GridMate/Serialize/ContainerMarshal.h>
#include <AzCore/RTTI/BehaviorObjectSignals.h>
namespace AZ
{
class ScriptProperty;
}
namespace AzFramework
{
/**
* Specalized helper marshaler for ScriptProperty class
*/
class ScriptPropertyMarshaler
{
public:
void Marshal(GridMate::WriteBuffer& wb, AZ::ScriptProperty*const& cont) const;
bool UnmarshalToPointer(AZ::ScriptProperty*& target, GridMate::ReadBuffer& rb) const;
};
class ScriptPropertyThrottler
{
public:
ScriptPropertyThrottler();
void SignalDirty();
bool WithinThreshold(AZ::ScriptProperty* newValue) const;
void UpdateBaseline(AZ::ScriptProperty* baseline);
private:
bool m_isDirty;
};
/**
* Specialized helper marshaler to help with the vector creation/destruction
*/
class ScriptRPCMarshaler
{
public:
typedef AZStd::vector< AZ::ScriptProperty* > Container;
ScriptRPCMarshaler()
{
}
AZ_FORCE_INLINE void Marshal(GridMate::WriteBuffer& wb, const Container& container) const
{
AZ_Assert(container.size() < USHRT_MAX, "Container has too many elements for marshaling!");
AZ::u16 size = static_cast<AZ::u16>(container.size());
wb.Write(size);
for (const auto& i : container)
{
m_marshaler.Marshal(wb, i);
}
}
AZ_FORCE_INLINE void Unmarshal(Container& container, GridMate::ReadBuffer& rb) const
{
container.clear();
AZ::u16 size;
rb.Read(size);
container.reserve(size);
for (AZ::u16 i = 0; i < size; ++i)
{
AZ::ScriptProperty* readProperty = nullptr;
m_marshaler.UnmarshalToPointer(readProperty, rb);
container.insert(container.end(), readProperty);
}
}
protected:
ScriptPropertyMarshaler m_marshaler;
};
}
#endif
File diff suppressed because it is too large Load Diff
@@ -1,320 +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.
*
*/
#ifndef AZFRAMEWORK_SCRIPT_NET_BINDINGS_H
#define AZFRAMEWORK_SCRIPT_NET_BINDINGS_H
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <GridMate/Replica/ReplicaChunkInterface.h>
#include <GridMate/Replica/DataSet.h>
#include <GridMate/Replica/RemoteProcedureCall.h>
#include <GridMate/Replica/RemoteProcedureCall.h>
#include <AzCore/Script/ScriptProperty.h>
#include <AzCore/Script/ScriptPropertyTable.h>
#include <AzCore/Script/ScriptPropertyWatcherBus.h>
#include <AzFramework/Script/ScriptMarshal.h>
namespace AzFramework
{
class ScriptPropertyDataSet;
class ScriptComponentReplicaChunk;
// ScriptNetBindingTable will act as the go between for the ScriptComponent and the Replica's.
// It will also allow for holding of values in the case where you haven't been bound to a replica chunk yet and the
// script tries to interact with something that is networked.
//
// Allows for scripts to be re-used seamlessly in a offline vs online scenario(and support for going from offline to online),
// including RPCs(will alawys call the master version if offline)
class ScriptNetBindingTable
: public GridMate::ReplicaChunkInterface
{
private:
friend class ScriptComponentReplicaChunk;
friend class ScriptPropertyDataSet;
// Helper struct to keep track of a a ScriptContext
// and the entityTableReference. Mainly used for
// calling in to functions in LUA where we want
// to push in the table reference as the first parameter
struct EntityScriptContext
{
public:
EntityScriptContext();
void Unload();
bool HasEntityTableRegistryIndex() const;
int GetEntityTableRegistryIndex() const;
bool HasScriptContext() const;
AZ::ScriptContext* GetScriptContext() const;
void ConfigureContext(AZ::ScriptContext* scriptContext, int entityTableRegistryIndex);
private:
bool SanityCheckContext() const;
AZ::ScriptContext* m_scriptContext;
int m_entityTableRegistryIndex;
};
class NetworkedTableValue;
friend NetworkedTableValue;
typedef AZStd::unordered_map<AZStd::string, NetworkedTableValue> NetworkedTableMap;
class RPCBindingHelper;
friend RPCBindingHelper;
typedef AZStd::unordered_map<AZStd::string, RPCBindingHelper> RPCHelperMap;
// Helper class that will wrap up our interactions with the actual stored value
// to hide the general use case of if we are connected to a replica or not.
//
// Additionally this will serve as a holding ground for a 'networked'
// value that doesn't have a dataset.
//
// Lastly holds onto the Callback references.
class NetworkedTableValue
{
public:
AZ_CLASS_ALLOCATOR(NetworkedTableValue, AZ::SystemAllocator, 0);
NetworkedTableValue(AZ::ScriptProperty* initialValue = nullptr);
~NetworkedTableValue();
void Destroy();
// Methods to register this value to a chunk
bool HasDataSet() const;
void RegisterDataSet(ScriptPropertyDataSet* dataSet);
void UnbindFromDataSet();
ScriptPropertyDataSet* GetDataSet() const;
// Information kept in order to force these values to use a particular dataset for debugging.
bool HasForcedDataSetIndex() const;
void SetForcedDataSetIndex(int index);
int GetForcedDataSetIndex() const;
// Callback functions
bool HasCallback() const;
void RegisterCallback(int functionReference);
void ReleaseCallback(AZ::ScriptContext& scriptContext);
void InvokeCallback(EntityScriptContext& scriptContext, const GridMate::TimeContext& timeContext);
bool AssignValue(AZ::ScriptDataContext& scriptDataContext, const AZStd::string& propertyName);
bool InspectValue(AZ::ScriptContext* scriptContext) const;
// Methods used for unit tests
const AZ::ScriptProperty* GetShimmedScriptProperty() const { return m_shimmedScriptProperty; }
private:
// This value will be used if we have a networked property, but don't have a valid chunk yet.
// Works as a temporary store, which will be resolved once we get assigned to a DataSet
AZ::ScriptProperty* m_shimmedScriptProperty;
// The data set we are bound to
ScriptPropertyDataSet* m_dataSet;
int m_forcedDataSetIndex;
int m_functionReference;
};
// Future thoughts
// - Move the actual RPC meta table creation
// into this guy
class RPCBindingHelper
{
public:
AZ_CLASS_ALLOCATOR(RPCBindingHelper, AZ::SystemAllocator, 0);
RPCBindingHelper();
~RPCBindingHelper();
void ReleaseTableIndex(AZ::ScriptContext& scriptContext);
bool IsValid() const;
void SetMasterFunction(int masterReference);
bool InvokeMaster(EntityScriptContext& entityScriptContext, const ScriptRPCMarshaler::Container& params);
void SetProxyFunction(int masterReference);
void InvokeProxy(EntityScriptContext& entityScriptContext, const ScriptRPCMarshaler::Container& params);
private:
int m_masterReference;
int m_proxyReference;
};
public:
AZ_CLASS_ALLOCATOR(ScriptNetBindingTable, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* reflect);
ScriptNetBindingTable();
~ScriptNetBindingTable();
void Unload();
void CreateNetworkBindingTable(AZ::ScriptContext* scriptContext, int baseTableIndex, int entityTableIndex);
void FinalizeNetworkTable(AZ::ScriptContext* scriptContext, int entityTableRegistryIndex);
AZ::ScriptContext* GetScriptContext() const;
bool IsMaster() const;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// DataSet Functionality
//
// Called when the script wants to bind a function callback to when
// a value changes
//
// Might change this to just be register DataSet
bool RegisterDataSet(AZ::ScriptDataContext& stackContext, AZ::ScriptProperty* scriptProperty);
// Called when the script wants to assign a value to the script value
bool AssignTableValue(AZ::ScriptDataContext& stackContext);
// Called when the script wants to know the value of a script value.
bool InspectTableValue(AZ::ScriptDataContext& stackContext) const;
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
/// RPC Functionality
void RegisterRPC(AZ::ScriptDataContext& rpcTableContext, const AZStd::string& rpcName, int elementIndex, int tableStackIndex);
bool InvokeRPC(AZ::ScriptDataContext& stackContext);
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Netbinding Interface duplication here to be called from the ScriptComponent
GridMate::ReplicaChunkPtr GetNetworkBinding();
void SetNetworkBinding(GridMate::ReplicaChunkPtr chunk);
void UnbindFromNetwork();
void OnPropertyUpdate(AZ::ScriptProperty*const& scriptProperty, const GridMate::TimeContext& tc);
bool OnInvokeRPC(AZStd::string functionName, AZStd::vector< AZ::ScriptProperty*> properties, const GridMate::RpcContext& rpcContext);
// Methods used for unit tests
const AZ::ScriptProperty* FindScriptProperty(const AZStd::string& name) const;
private:
void RegisterMetaTableCache();
template<typename PropertyType, typename PropertyArrayType>
AZ::ScriptPropertyTable* ConvertPropertyArrayToTable(PropertyArrayType* arrayProperty)
{
AZ::ScriptPropertyTable* scriptPropertyTable = aznew AZ::ScriptPropertyTable(arrayProperty->m_name.c_str());
PropertyType propertyType;
for (unsigned int i=0; i < arrayProperty->m_values.size(); ++i)
{
propertyType.m_value = arrayProperty->m_values[i];
// Offset by 1 to deal with lua 1 indexing.
// Table will make a clone of our object.
scriptPropertyTable->SetTableValue(i+1, &propertyType);
}
return scriptPropertyTable;
}
void AssignDataSets();
NetworkedTableValue* FindTableValue(const AZStd::string& name);
const NetworkedTableValue* FindTableValue(const AZStd::string& name) const;
EntityScriptContext m_entityScriptContext;
GridMate::ReplicaChunkPtr m_replicaChunk;
NetworkedTableMap m_networkedTable;
RPCHelperMap m_rpcHelperMap;
};
// Typedeffing out the RPC and DataSet definitions.
typedef GridMate::Rpc< GridMate::RpcArg< AZStd::string >, GridMate::RpcArg< ScriptRPCMarshaler::Container, ScriptRPCMarshaler > >::BindInterface<ScriptNetBindingTable, &ScriptNetBindingTable::OnInvokeRPC> ScriptPropertyRPC;
typedef GridMate::DataSet<AZ::ScriptProperty*, ScriptPropertyMarshaler, ScriptPropertyThrottler>::BindInterface<ScriptNetBindingTable, &ScriptNetBindingTable::OnPropertyUpdate> ScriptPropertyDataSetType;
class ScriptComponentReplicaChunk;
// Specialized DataSet used by the ScriptProperties, just to add some wrapped around functionality
// and to allow me to manipulate the DataSet throttler in order to properly manage a dirty flag
class ScriptPropertyDataSet
: public ScriptPropertyDataSetType
, public AZ::ScriptPropertyWatcherBus::Handler
, public AZ::ScriptPropertyWatcher
{
private:
friend class ScriptComponentReplicaChunk;
friend class ScriptNetBindingTable::NetworkedTableValue;
const char* GetDataSetName();
public:
ScriptPropertyDataSet();
~ScriptPropertyDataSet();
bool IsReserved() const;
bool UpdateScriptProperty(AZ::ScriptDataContext& scriptDataContext, const AZStd::string& propertyName);
void SetScriptProperty(AZ::ScriptProperty* scriptProperty);
void OnObjectModified() override;
private:
void Reserve(ScriptNetBindingTable::NetworkedTableValue* reserver);
void Release(ScriptNetBindingTable::NetworkedTableValue* reserver);
ScriptNetBindingTable::NetworkedTableValue* m_reserver;
};
// The actual ReplicaChunk that the script will use
class ScriptComponentReplicaChunk
: public GridMate::ReplicaChunkBase
{
public:
AZ_CLASS_ALLOCATOR(ScriptComponentReplicaChunk, AZ::SystemAllocator,0);
static const int k_maxScriptableDataSets = GM_MAX_DATASETS_IN_CHUNK;
static const char* GetChunkName() { return "ScriptComponentReplicaChunk"; }
// Might want to add some type of comment field into the various fields so this can be properly parsed
// and determined what we are actually sending.
ScriptComponentReplicaChunk();
~ScriptComponentReplicaChunk();
bool IsReplicaMigratable() override;
AZ::u32 CalculateDirtyDataSetMask(GridMate::MarshalContext& marshalContext) override;
// Called from the Master, will assign the table value to the DataSet specified by the helper.
bool AssignDataSet(ScriptNetBindingTable::NetworkedTableValue& helper);
// Called from teh Proxy. Will Assign the TableValue to the DataSet that contains the target property
void AssignDataSetForProperty(ScriptNetBindingTable::NetworkedTableValue& helper, AZ::ScriptProperty* targetProperty);
// Only called inside of an assert, checks that the DataSet that the targetProperty is in is the same as the assumedDataSet
// Used to confirm that we don't get a confusion between master/proxy about which ScriptProperty is assigned to which DataSet.
bool SanityCheckDataSet(AZ::ScriptProperty* targetProperty, ScriptPropertyDataSet* assumedDataSet);
ScriptPropertyRPC m_scriptRPC;
private:
AZ::u32 m_enabledDataSetMask;
ScriptPropertyDataSet m_propertyDataSets[k_maxScriptableDataSets];
};
}
#endif
@@ -177,7 +177,7 @@ namespace AzFramework
Neighborhood::NeighborReplicaPtr replicaChunk = GridMate::CreateReplicaChunk<Neighborhood::NeighborReplica>(session->GetMyMember()->GetId().Compact(), m_component->m_settings->m_persistentName.c_str(), Neighborhood::NEIGHBOR_CAP_LUA_VM | Neighborhood::NEIGHBOR_CAP_LUA_DEBUGGER);
replicaChunk->SetDisplayName(m_component->m_settings->m_persistentName.c_str());
replica->AttachReplicaChunk(replicaChunk);
session->GetReplicaMgr()->AddMaster(replica);
session->GetReplicaMgr()->AddPrimary(replica);
}
}
@@ -0,0 +1,54 @@
/*
* 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.
*
*/
// Description : Ebus for querying thermal information of the device.
#pragma once
#include <AzCore/EBus/EBus.h>
// The different types of temperature sensor that could be available on the device.
enum class ThermalSensorType : int
{
CPU = 0,
GPU,
Battery,
Count
};
// Handles requests for thermal information
class ThermalInfoHandler : public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
virtual ~ThermalInfoHandler() = default;
/**
* Returns the current temperature of a specific sensor in Celcius degrees.
* If the type of sensor is not available on the device it returns 0.
*
* \param sensor The sensor type.
*/
virtual float GetSensorTemp(ThermalSensorType sensor) = 0;
/**
* Returns the temperature that is considered as overheating for a specific sensor.
* The value returned is in Celcius degrees.
*
* \param sensor The sensor type.
*/
virtual float GetSensorOverheatingTemp(ThermalSensorType sensor) = 0;
};
using ThermalInfoRequestsBus = AZ::EBus<ThermalInfoHandler>;
@@ -15,6 +15,7 @@
#include <AzCore/Console/IConsole.h>
#include <AzCore/Math/MathUtils.h>
#include <AzCore/Math/Plane.h>
#include <AzCore/std/numeric.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Windowing/WindowBus.h>
@@ -29,14 +30,14 @@ namespace AzFramework
AZ_CVAR(float, ed_cameraSystemOrbitDollyScrollSpeed, 0.02f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(float, ed_cameraSystemOrbitDollyCursorSpeed, 0.01f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(float, ed_cameraSystemScrollTranslateSpeed, 0.02f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(float, ed_cameraSystemDefaultOrbitDistance, 60.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(float, ed_cameraSystemMaxOrbitDistance, 100.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(float, ed_cameraSystemMaxOrbitDistance, 60.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(float, ed_cameraSystemLookSmoothness, 5.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(float, ed_cameraSystemTranslateSmoothness, 5.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(float, ed_cameraSystemRotateSpeed, 0.005f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(float, ed_cameraSystemPanSpeed, 0.01f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(bool, ed_cameraSystemPanInvertX, true, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(bool, ed_cameraSystemPanInvertY, true, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(float, ed_cameraSystemLookDeadzone, 2.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ_CVAR(
AZ::CVarFixedString, ed_cameraSystemTranslateForwardKey, "keyboard_key_alphanumeric_W", nullptr, AZ::ConsoleFunctorFlags::Null, "");
@@ -125,22 +126,22 @@ namespace AzFramework
{
if (orientation.GetElement(2, 0) > -1.0f)
{
x = std::atan2(orientation.GetElement(2, 1), orientation.GetElement(2, 2));
y = std::asin(-orientation.GetElement(2, 0));
z = std::atan2(orientation.GetElement(1, 0), orientation.GetElement(0, 0));
x = AZStd::atan2(orientation.GetElement(2, 1), orientation.GetElement(2, 2));
y = AZStd::asin(-orientation.GetElement(2, 0));
z = AZStd::atan2(orientation.GetElement(1, 0), orientation.GetElement(0, 0));
}
else
{
x = 0.0f;
y = AZ::Constants::Pi * 0.5f;
z = -std::atan2(-orientation.GetElement(2, 1), orientation.GetElement(1, 1));
z = -AZStd::atan2(-orientation.GetElement(2, 1), orientation.GetElement(1, 1));
}
}
else
{
x = 0.0f;
y = -AZ::Constants::Pi * 0.5f;
z = std::atan2(-orientation.GetElement(1, 2), orientation.GetElement(1, 1));
z = AZStd::atan2(-orientation.GetElement(1, 2), orientation.GetElement(1, 1));
}
return {x, y, z};
@@ -150,37 +151,31 @@ namespace AzFramework
{
const auto eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromTransform(transform));
camera.m_lookAt = transform.GetTranslation();
camera.m_pitch = eulerAngles.GetX();
camera.m_yaw = eulerAngles.GetZ();
// note: m_lookDist is negative so we must invert it here
camera.m_lookAt = transform.GetTranslation() + (camera.Rotation().GetBasisY() * -camera.m_lookDist);
}
bool CameraSystem::HandleEvents(const InputEvent& event)
{
if (const auto& cursor_motion = AZStd::get_if<CursorMotionEvent>(&event))
if (const auto& cursor = AZStd::get_if<CursorEvent>(&event))
{
m_currentCursorPosition = cursor_motion->m_position;
m_cursorState.SetCurrentPosition(cursor->m_position);
}
else if (const auto& scroll = AZStd::get_if<ScrollEvent>(&event))
{
m_scrollDelta = scroll->m_delta;
}
return m_cameras.HandleEvents(event);
return m_cameras.HandleEvents(event, m_cursorState.CursorDelta(), m_scrollDelta);
}
Camera CameraSystem::StepCamera(const Camera& targetCamera, const float deltaTime)
{
const auto cursorDelta = m_currentCursorPosition.has_value() && m_lastCursorPosition.has_value()
? m_currentCursorPosition.value() - m_lastCursorPosition.value()
: ScreenVector(0, 0);
const auto nextCamera = m_cameras.StepCamera(targetCamera, m_cursorState.CursorDelta(), m_scrollDelta, deltaTime);
if (m_currentCursorPosition.has_value())
{
m_lastCursorPosition = m_currentCursorPosition;
}
const auto nextCamera = m_cameras.StepCamera(targetCamera, cursorDelta, m_scrollDelta, deltaTime);
m_cursorState.Update();
m_scrollDelta = 0.0f;
@@ -192,18 +187,18 @@ namespace AzFramework
m_idleCameraInputs.push_back(AZStd::move(cameraInput));
}
bool Cameras::HandleEvents(const InputEvent& event)
bool Cameras::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta)
{
bool handling = false;
for (auto& cameraInput : m_activeCameraInputs)
{
cameraInput->HandleEvents(event);
cameraInput->HandleEvents(event, cursorDelta, scrollDelta);
handling = !cameraInput->Idle() || handling;
}
for (auto& cameraInput : m_idleCameraInputs)
{
cameraInput->HandleEvents(event);
cameraInput->HandleEvents(event, cursorDelta, scrollDelta);
}
return handling;
@@ -215,8 +210,8 @@ namespace AzFramework
{
auto& cameraInput = m_idleCameraInputs[i];
const bool canBegin = cameraInput->Beginning() &&
std::all_of(m_activeCameraInputs.cbegin(), m_activeCameraInputs.cend(),
[](const auto& input) { return !input->Exclusive(); }) &&
AZStd::all_of(m_activeCameraInputs.cbegin(), m_activeCameraInputs.cend(),
[](const auto& input) { return !input->Exclusive(); }) &&
(!cameraInput->Exclusive() || (cameraInput->Exclusive() && m_activeCameraInputs.empty()));
if (canBegin)
@@ -232,12 +227,12 @@ namespace AzFramework
}
}
// accumulate
Camera nextCamera = targetCamera;
for (auto& cameraInput : m_activeCameraInputs)
{
nextCamera = cameraInput->StepCamera(nextCamera, cursorDelta, scrollDelta, deltaTime);
}
const Camera nextCamera = AZStd::accumulate(
AZStd::begin(m_activeCameraInputs), AZStd::end(m_activeCameraInputs), targetCamera,
[cursorDelta, scrollDelta, deltaTime](Camera acc, auto& camera) {
acc = camera->StepCamera(acc, cursorDelta, scrollDelta, deltaTime);
return acc;
});
for (int i = 0; i < m_activeCameraInputs.size();)
{
@@ -271,21 +266,42 @@ namespace AzFramework
}
}
void RotateCameraInput::HandleEvents(const InputEvent& event)
RotateCameraInput::RotateCameraInput(const InputChannelId rotateChannelId)
: m_rotateChannelId(rotateChannelId)
{
if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
{
if (input->m_channelId == m_rotateChannelId)
}
void RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
const ClickDetector::ClickEvent clickEvent = [&event, this] {
if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
{
if (input->m_state == InputChannel::State::Began)
if (input->m_channelId == m_rotateChannelId)
{
BeginActivation();
}
else if (input->m_state == InputChannel::State::Ended)
{
EndActivation();
if (input->m_state == InputChannel::State::Began)
{
return ClickDetector::ClickEvent::Down;
}
else if (input->m_state == InputChannel::State::Ended)
{
return ClickDetector::ClickEvent::Up;
}
}
}
return ClickDetector::ClickEvent::Nil;
}();
switch (const auto outcome = m_clickDetector.DetectClick(clickEvent, cursorDelta); outcome)
{
case ClickDetector::ClickOutcome::Move:
BeginActivation();
break;
case ClickDetector::ClickOutcome::Release:
EndActivation();
break;
default:
// noop
break;
}
}
@@ -298,7 +314,7 @@ namespace AzFramework
nextCamera.m_pitch -= float(cursorDelta.m_y) * ed_cameraSystemRotateSpeed;
nextCamera.m_yaw -= float(cursorDelta.m_x) * ed_cameraSystemRotateSpeed;
const auto clampRotation = [](const float angle) { return std::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); };
const auto clampRotation = [](const float angle) { return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); };
nextCamera.m_yaw = clampRotation(nextCamera.m_yaw);
// clamp pitch to be +-90 degrees
@@ -307,7 +323,14 @@ namespace AzFramework
return nextCamera;
}
void PanCameraInput::HandleEvents(const InputEvent& event)
PanCameraInput::PanCameraInput(const InputChannelId panChannelId, PanAxesFn panAxesFn)
: m_panAxesFn(AZStd::move(panAxesFn))
, m_panChannelId(panChannelId)
{
}
void PanCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
{
@@ -382,17 +405,18 @@ namespace AzFramework
return TranslationType::Nil;
}
void TranslateCameraInput::HandleEvents(const InputEvent& event)
TranslateCameraInput::TranslateCameraInput(TranslationAxesFn translationAxesFn)
: m_translationAxesFn(AZStd::move(translationAxesFn))
{
}
void TranslateCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
{
if (input->m_state == InputChannel::State::Began)
{
if (input->m_state == InputChannel::State::Updated)
{
return;
}
m_translation |= translationFromKey(input->m_channelId);
if (m_translation != TranslationType::Nil)
{
@@ -478,7 +502,7 @@ namespace AzFramework
m_boost = false;
}
void OrbitCameraInput::HandleEvents(const InputEvent& event)
void OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta)
{
if (const auto* input = AZStd::get_if<DiscreteInputEvent>(&event))
{
@@ -497,7 +521,7 @@ namespace AzFramework
if (Active())
{
m_orbitCameras.HandleEvents(event);
m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta);
}
}
@@ -509,8 +533,10 @@ namespace AzFramework
if (Beginning())
{
float hit_distance = 0.0f;
if (AZ::Plane::CreateFromNormalAndPoint(AZ::Vector3::CreateAxisZ(), AZ::Vector3::CreateAxisZ(ed_cameraSystemDefaultPlaneHeight))
.CastRay(targetCamera.Translation(), targetCamera.Rotation().GetBasisY(), hit_distance))
AZ::Plane::CreateFromNormalAndPoint(AZ::Vector3::CreateAxisZ(), AZ::Vector3::CreateAxisZ(ed_cameraSystemDefaultPlaneHeight))
.CastRay(targetCamera.Translation(), targetCamera.Rotation().GetBasisY(), hit_distance);
if (hit_distance > 0.0f)
{
hit_distance = AZStd::min<float>(hit_distance, ed_cameraSystemMaxOrbitDistance);
nextCamera.m_lookDist = -hit_distance;
@@ -539,7 +565,8 @@ namespace AzFramework
return nextCamera;
}
void OrbitDollyScrollCameraInput::HandleEvents(const InputEvent& event)
void OrbitDollyScrollCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto* scroll = AZStd::get_if<ScrollEvent>(&event))
{
@@ -557,7 +584,13 @@ namespace AzFramework
return nextCamera;
}
void OrbitDollyCursorMoveCameraInput::HandleEvents(const InputEvent& event)
OrbitDollyCursorMoveCameraInput::OrbitDollyCursorMoveCameraInput(const InputChannelId dollyChannelId)
: m_dollyChannelId(dollyChannelId)
{
}
void OrbitDollyCursorMoveCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
{
@@ -584,7 +617,8 @@ namespace AzFramework
return nextCamera;
}
void ScrollTranslationCameraInput::HandleEvents(const InputEvent& event)
void ScrollTranslationCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto* scroll = AZStd::get_if<ScrollEvent>(&event))
{
@@ -610,7 +644,7 @@ namespace AzFramework
Camera SmoothCamera(const Camera& currentCamera, const Camera& targetCamera, const float deltaTime)
{
const auto clamp_rotation = [](const float angle) { return std::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); };
const auto clamp_rotation = [](const float angle) { return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); };
// keep yaw in 0 - 360 range
float targetYaw = clamp_rotation(targetCamera.m_yaw);
@@ -621,7 +655,7 @@ namespace AzFramework
// ensure smooth transition when moving across 0 - 360 boundary
const float yawDelta = targetYaw - currentYaw;
if (std::abs(yawDelta) >= AZ::Constants::Pi)
if (AZStd::abs(yawDelta) >= AZ::Constants::Pi)
{
targetYaw -= AZ::Constants::TwoPi * sign(yawDelta);
}
@@ -629,12 +663,12 @@ namespace AzFramework
Camera camera;
// note: the math for the lerp smoothing implementation for camera rotation and translation was inspired by this excellent
// article by Scott Lembcke: https://www.gamasutra.com/blogs/ScottLembcke/20180404/316046/Improved_Lerp_Smoothing.php
const float lookRate = std::exp2(ed_cameraSystemLookSmoothness);
const float lookT = std::exp2(-lookRate * deltaTime);
const float lookRate = AZStd::exp2(ed_cameraSystemLookSmoothness);
const float lookT = AZStd::exp2(-lookRate * deltaTime);
camera.m_pitch = AZ::Lerp(targetCamera.m_pitch, currentCamera.m_pitch, lookT);
camera.m_yaw = AZ::Lerp(targetYaw, currentYaw, lookT);
const float moveRate = std::exp2(ed_cameraSystemTranslateSmoothness);
const float moveT = std::exp2(-moveRate * deltaTime);
const float moveRate = AZStd::exp2(ed_cameraSystemTranslateSmoothness);
const float moveT = AZStd::exp2(-moveRate * deltaTime);
camera.m_lookDist = AZ::Lerp(targetCamera.m_lookDist, currentCamera.m_lookDist, moveT);
camera.m_lookAt = targetCamera.m_lookAt.Lerp(currentCamera.m_lookAt, moveT);
return camera;
@@ -655,7 +689,7 @@ namespace AzFramework
const auto* position = inputChannel.GetCustomData<AzFramework::InputChannel::PositionData2D>();
AZ_Assert(position, "Expected PositionData2D but found nullptr");
return CursorMotionEvent{ScreenPoint(
return CursorEvent{ScreenPoint(
position->m_normalizedPosition.GetX() * windowSize.m_width, position->m_normalizedPosition.GetY() * windowSize.m_height)};
}
else if (inputChannelId == InputDeviceMouse::Movement::Z)
@@ -17,6 +17,8 @@
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/optional.h>
#include <AzFramework/Input/Channels/InputChannel.h>
#include <AzFramework/Viewport/ClickDetector.h>
#include <AzFramework/Viewport/CursorState.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
#include <AzFramework/Viewport/ViewportId.h>
@@ -70,7 +72,7 @@ namespace AzFramework
void UpdateCameraFromTransform(Camera& camera, const AZ::Transform& transform);
struct CursorMotionEvent
struct CursorEvent
{
ScreenPoint m_position;
};
@@ -86,7 +88,7 @@ namespace AzFramework
InputChannel::State m_state; //!< Channel state. (e.g. Begin/update/end event).
};
using InputEvent = AZStd::variant<AZStd::monostate, CursorMotionEvent, ScrollEvent, DiscreteInputEvent>;
using InputEvent = AZStd::variant<AZStd::monostate, CursorEvent, ScrollEvent, DiscreteInputEvent>;
class CameraInput
{
@@ -147,7 +149,7 @@ namespace AzFramework
ResetImpl();
}
virtual void HandleEvents(const InputEvent& event) = 0;
virtual void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) = 0;
virtual Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) = 0;
virtual bool Exclusive() const
@@ -170,7 +172,7 @@ namespace AzFramework
{
public:
void AddCamera(AZStd::shared_ptr<CameraInput> cameraInput);
bool HandleEvents(const InputEvent& event);
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta);
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime);
void Reset();
@@ -188,24 +190,21 @@ namespace AzFramework
Cameras m_cameras;
private:
CursorState m_cursorState;
float m_scrollDelta = 0.0f;
AZStd::optional<ScreenPoint> m_lastCursorPosition;
AZStd::optional<ScreenPoint> m_currentCursorPosition;
};
class RotateCameraInput : public CameraInput
{
public:
explicit RotateCameraInput(const InputChannelId rotateChannelId)
: m_rotateChannelId(rotateChannelId)
{
}
explicit RotateCameraInput(InputChannelId rotateChannelId);
void HandleEvents(const InputEvent& event) override;
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
InputChannelId m_rotateChannelId;
ClickDetector m_clickDetector;
};
struct PanAxes
@@ -238,12 +237,9 @@ namespace AzFramework
class PanCameraInput : public CameraInput
{
public:
PanCameraInput(const InputChannelId panChannelId, PanAxesFn panAxesFn)
: m_panAxesFn(AZStd::move(panAxesFn))
, m_panChannelId(panChannelId)
{
}
void HandleEvents(const InputEvent& event) override;
PanCameraInput(InputChannelId panChannelId, PanAxesFn panAxesFn);
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
@@ -281,11 +277,9 @@ namespace AzFramework
class TranslateCameraInput : public CameraInput
{
public:
explicit TranslateCameraInput(TranslationAxesFn translationAxesFn)
: m_translationAxesFn(AZStd::move(translationAxesFn))
{
}
void HandleEvents(const InputEvent& event) override;
explicit TranslateCameraInput(TranslationAxesFn translationAxesFn);
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
void ResetImpl() override;
@@ -354,17 +348,16 @@ namespace AzFramework
class OrbitDollyScrollCameraInput : public CameraInput
{
public:
void HandleEvents(const InputEvent& event) override;
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
};
class OrbitDollyCursorMoveCameraInput : public CameraInput
{
public:
explicit OrbitDollyCursorMoveCameraInput(const InputChannelId dollyChannelId)
: m_dollyChannelId(dollyChannelId) {}
explicit OrbitDollyCursorMoveCameraInput(InputChannelId dollyChannelId);
void HandleEvents(const InputEvent& event) override;
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
@@ -374,14 +367,14 @@ namespace AzFramework
class ScrollTranslationCameraInput : public CameraInput
{
public:
void HandleEvents(const InputEvent& event) override;
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
};
class OrbitCameraInput : public CameraInput
{
public:
void HandleEvents(const InputEvent& event) override;
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
bool Exclusive() const override
{
@@ -0,0 +1,68 @@
/*
* 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/Viewport/ClickDetector.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
namespace AzFramework
{
ClickDetector::ClickOutcome ClickDetector::DetectClick(const ClickEvent clickEvent, const ScreenVector& cursorDelta)
{
if (clickEvent == ClickEvent::Down)
{
const auto now = std::chrono::steady_clock::now();
if (m_tryBeginTime)
{
const std::chrono::duration<float> diff = now - m_tryBeginTime.value();
if (diff.count() < m_doubleClickInterval)
{
return ClickOutcome::Nil;
}
}
m_detectionState = DetectionState::WaitingForMove;
m_moveAccumulator = 0.0f;
m_tryBeginTime = now;
}
else if (clickEvent == ClickEvent::Up)
{
const auto clickOutcome = [detectionState = m_detectionState] {
if (detectionState == DetectionState::WaitingForMove)
{
return ClickOutcome::Click;
}
if (detectionState == DetectionState::Moved)
{
return ClickOutcome::Release;
}
return ClickOutcome::Nil;
}();
m_detectionState = DetectionState::Nil;
return clickOutcome;
}
if (m_detectionState == DetectionState::WaitingForMove)
{
// only allow the action to begin if the mouse has been moved a small amount
m_moveAccumulator += ScreenVectorLength(cursorDelta);
if (m_moveAccumulator > m_deadZone)
{
m_detectionState = DetectionState::Moved;
return ClickOutcome::Move;
}
}
return ClickOutcome::Nil;
}
} // namespace AzFramework
@@ -0,0 +1,75 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/optional.h>
#include <chrono>
namespace AzFramework
{
struct ScreenVector;
//! Utility class to help detect different types of mouse click (mouse down and up with
//! no movement), mouse move (down and initial move after some threshold) and mouse release
//! (mouse down with movement and then mouse up).
class ClickDetector
{
//! Alias for recording time of mouse down events
using Time = std::chrono::time_point<std::chrono::steady_clock>;
public:
//! Internal representation of click event (map from external event for this when
//! calling DetectClick).
enum class ClickEvent
{
Nil,
Down,
Up
};
//! The type of mouse click.
enum class ClickOutcome
{
Nil, //!< Not recognized.
Move, //!< Initial move after mouse down.
Click, //!< Mouse down and up with no intermediate movement.
Release //!< Mouse down with movement and then mouse up.
};
//! Called from any type of 'handle event' function.
ClickOutcome DetectClick(ClickEvent clickEvent, const ScreenVector& cursorDelta);
void SetDoubleClickInterval(float doubleClickInterval);
private:
//! Internal state of ClickDetector based on incoming events.
enum class DetectionState
{
Nil, //!< Initial state
WaitingForMove, //! Mouse down has happened but mouse hasn't yet moved.
Moved //! Mouse has moved, no longer will be counted as a click.
};
float m_moveAccumulator = 0.0f; //!< How far the mouse has moved after mouse down.
float m_deadZone = 2.0f; //!< How far to move before a click is cancelled (when Move will fire).
float m_doubleClickInterval = 0.4f; //!< Default double click interval, can be overridden.
DetectionState m_detectionState; //!< Internal state of ClickDetector.
AZStd::optional<Time> m_tryBeginTime; //!< Mouse down time (happens each mouse down, helps with double click handling).
};
inline void ClickDetector::SetDoubleClickInterval(const float doubleClickInterval)
{
m_doubleClickInterval = doubleClickInterval;
}
} // namespace AzFramework
@@ -0,0 +1,56 @@
/*
* 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 <AzFramework/Viewport/ScreenGeometry.h>
#include <AzCore/std/optional.h>
namespace AzFramework
{
//! Utility type to wrap a current and last cursor position.
struct CursorState
{
//! Returns the delta between the current and last cursor position.
[[nodiscard]] ScreenVector CursorDelta() const;
//! Call this in a 'handle event' call to update the most recent cursor position.
void SetCurrentPosition(const ScreenPoint& currentPosition);
//! Call this in an 'update' call to copy the current cursor position to the last
//! cursor position.
void Update();
private:
AZStd::optional<ScreenPoint> m_lastCursorPosition;
AZStd::optional<ScreenPoint> m_currentCursorPosition;
};
inline void CursorState::SetCurrentPosition(const ScreenPoint& currentPosition)
{
m_currentCursorPosition = currentPosition;
}
inline ScreenVector CursorState::CursorDelta() const
{
return m_currentCursorPosition.has_value() && m_lastCursorPosition.has_value()
? m_currentCursorPosition.value() - m_lastCursorPosition.value()
: ScreenVector(0, 0);
}
inline void CursorState::Update()
{
if (m_currentCursorPosition.has_value())
{
m_lastCursorPosition = m_currentCursorPosition;
}
}
} // namespace AzFramework
@@ -134,11 +134,16 @@ namespace AzFramework
return !operator==(lhs, rhs);
}
inline float ScreenVectorLength(const ScreenVector& screenVector)
{
return aznumeric_cast<float>(AZStd::sqrt(screenVector.m_x * screenVector.m_x + screenVector.m_y * screenVector.m_y));
}
inline ScreenPoint ScreenPointFromNDC(const AZ::Vector3& screenNDC, const AZ::Vector2& viewportSize)
{
return ScreenPoint(
aznumeric_caster(std::round(screenNDC.GetX() * viewportSize.GetX())),
aznumeric_caster(std::round((1.0f - screenNDC.GetY()) * viewportSize.GetY())));
aznumeric_caster(AZStd::round(screenNDC.GetX() * viewportSize.GetX())),
aznumeric_caster(AZStd::round((1.0f - screenNDC.GetY()) * viewportSize.GetY())));
}
inline AZ::Vector2 NDCFromScreenPoint(const ScreenPoint& screenPoint, const AZ::Vector2& viewportSize)
@@ -103,6 +103,9 @@ set(FILES
Viewport/CameraState.cpp
Viewport/CameraInput.h
Viewport/CameraInput.cpp
Viewport/ClickDetector.h
Viewport/ClickDetector.cpp
Viewport/CursorState.h
Viewport/DisplayContextRequestBus.h
Entity/BehaviorEntity.cpp
Entity/BehaviorEntity.h
@@ -161,26 +164,6 @@ set(FILES
Metrics/MetricsPlainTextNameRegistration.h
Network/AssetProcessorConnection.cpp
Network/AssetProcessorConnection.h
Network/DynamicSerializableFieldMarshaler.h
Network/EntityIdMarshaler.h
Network/InterestManagerComponent.h
Network/InterestManagerComponent.cpp
Network/NetBindable.h
Network/NetBindable.cpp
Network/NetBindingEventsBus.h
Network/NetBindingHandlerBus.h
Network/NetBindingSystemBus.h
Network/NetBindingComponent.h
Network/NetBindingComponent.cpp
Network/NetBindingComponentChunk.h
Network/NetBindingComponentChunk.cpp
Network/NetBindingSystemImpl.h
Network/NetBindingSystemImpl.cpp
Network/NetBindingSystemComponent.h
Network/NetBindingSystemComponent.cpp
Network/NetworkContext.h
Network/NetworkContext.cpp
Network/NetSystemBus.h
Network/SocketConnection.cpp
Network/SocketConnection.h
Logging/LogFile.cpp
@@ -203,10 +186,6 @@ set(FILES
Script/ScriptDebugAgentBus.h
Script/ScriptDebugMsgReflection.cpp
Script/ScriptDebugMsgReflection.h
Script/ScriptMarshal.h
Script/ScriptMarshal.cpp
Script/ScriptNetBindings.h
Script/ScriptNetBindings.cpp
Script/ScriptRemoteDebugging.cpp
Script/ScriptRemoteDebugging.h
StreamingInstall/StreamingInstall.h
@@ -279,6 +258,7 @@ set(FILES
Physics/ClassConverters.cpp
Physics/ClassConverters.h
Physics/MaterialBus.h
Physics/WindBus.h
Process/ProcessCommunicator.cpp
Process/ProcessCommunicator.h
Process/ProcessWatcher.cpp
@@ -316,6 +296,7 @@ set(FILES
Spawnable/SpawnableSystemComponent.cpp
Terrain/TerrainDataRequestBus.h
Terrain/TerrainDataRequestBus.cpp
Thermal/ThermalInfo.h
Platform/PlatformDefaults.h
Windowing/WindowBus.h
Windowing/NativeWindow.cpp
@@ -13,6 +13,7 @@
#include <AzFramework/API/ApplicationAPI_Platform.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzFramework/Thermal/ThermalInfo_Android.h>
#include <AzCore/Android/AndroidEnv.h>
#include <AzCore/Android/JNI/Object.h>
@@ -95,6 +96,7 @@ namespace AzFramework
private:
AndroidEventDispatcher* m_eventDispatcher;
ApplicationLifecycleEvents::Event m_lastEvent;
AZStd::unique_ptr<ThermalInfoHandler> m_thermalInfoHandler;
AZStd::atomic<bool> m_requestResponseReceived;
AZStd::unique_ptr<AZ::Android::JNI::Object> m_lumberyardActivity;
@@ -125,6 +127,10 @@ namespace AzFramework
AndroidLifecycleEvents::Bus::Handler::BusConnect();
AndroidAppRequests::Bus::Handler::BusConnect();
PermissionRequestResultNotification::Bus::Handler::BusConnect();
#if !defined(AZ_RELEASE_BUILD)
m_thermalInfoHandler = AZStd::make_unique<ThermalInfoAndroidHandler>();
#endif
}
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,124 @@
/*
* 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.
*
*/
#if !defined(AZ_RELEASE_BUILD)
#include "ThermalInfo_Android.h"
#include <AzCore/std/string/string.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <cstdio>
#include <sys/types.h>
#include <dirent.h>
ThermalInfoAndroidHandler::ThermalInfoAndroidHandler()
{
static_assert(AZ_ARRAY_SIZE(m_temperatureFiles) == static_cast<int>(ThermalSensorType::Count), "Thermal count does not match temperature array size");
ThermalInfoRequestsBus::Handler::BusConnect();
memset(m_temperatureFiles, 0, sizeof(m_temperatureFiles));
const int sensorCount = static_cast<int>(ThermalSensorType::Count);
const char* sensorTypes[sensorCount] = { "cpu", "gpu", "battery" };
const int maxStringLen = 128;
char tempString[maxStringLen];
const char* thermalPath = "/sys/class/thermal";
// List the elements from the thermal folder to get the thermal_zones available on the device
DIR* directory = opendir(thermalPath);
if (directory)
{
struct dirent* item;
const char* thermalPrefix = "thermal_zone";
// List all items of the directory and find the one that start with thermal_zone (thermal_zone0, thermal_zone1, etc)
while ((item = readdir(directory)) != nullptr)
{
if (strncmp(item->d_name, thermalPrefix, strlen(thermalPrefix)) == 0)
{
// Try to deduce the type of sensor. For this we read the "type" file of the thermal zone.
// This "type" is a string set by the manufacturer, so it can be anything.
AZStd::string path = AZStd::string::format("%s/%s/type", thermalPath, item->d_name);
FILE* sensorTypeFile = fopen(path.c_str(), "r");
if (sensorTypeFile)
{
if (fscanf(sensorTypeFile, "%s", tempString))
{
for (int i = 0; i < sensorCount; ++i)
{
if (m_temperatureFiles[i])
{
continue;
}
size_t foundPos = AzFramework::StringFunc::Find(tempString, sensorTypes[i]);
if (foundPos != AZStd::string::npos)
{
path = AZStd::string::format("%s/%s/temp", thermalPath, item->d_name);
m_temperatureFiles[i] = fopen(path.c_str(), "r");
break;
}
}
}
fclose(sensorTypeFile);
}
}
}
closedir(directory);
}
int cpuSensorIndex = static_cast<int>(ThermalSensorType::CPU);
if (!m_temperatureFiles[cpuSensorIndex])
{
// If we didn't find the CPU sensor just assume it's the first one.
AZStd::string path = AZStd::string::format("%s/thermal_zone0/temp", thermalPath);
m_temperatureFiles[cpuSensorIndex] = fopen(path.c_str(), "r");
}
}
ThermalInfoAndroidHandler::~ThermalInfoAndroidHandler()
{
ThermalInfoRequestsBus::Handler::BusDisconnect();
for (int i = 0; i < static_cast<int>(ThermalSensorType::Count); ++i)
{
if (m_temperatureFiles[i])
{
fclose(m_temperatureFiles[i]);
}
}
}
float ThermalInfoAndroidHandler::GetSensorTemp(ThermalSensorType sensor)
{
FILE* tempFile = m_temperatureFiles[static_cast<int>(sensor)];
if (!tempFile)
{
return 0.f;
}
fseek(tempFile, 0, SEEK_SET);
float temperature = 0.f;
fscanf(tempFile, "%f", &temperature);
temperature /= 1000.0f;
return temperature;
}
float ThermalInfoAndroidHandler::GetSensorOverheatingTemp(ThermalSensorType sensor)
{
const int overheatingTemperatures[static_cast<int>(ThermalSensorType::Count)] =
{
70, // CPU
70, // GPU
40 // Battery
};
return overheatingTemperatures[static_cast<int>(sensor)];
}
#endif // !defined(AZ_RELEASE_BUILD)
@@ -0,0 +1,30 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(AZ_RELEASE_BUILD)
#include <AzFramework/Thermal/ThermalInfo.h>
class ThermalInfoAndroidHandler : public ThermalInfoRequestsBus::Handler
{
public:
ThermalInfoAndroidHandler();
~ThermalInfoAndroidHandler() override;
float GetSensorTemp(ThermalSensorType sensor) override;
float GetSensorOverheatingTemp(ThermalSensorType sensor) override;
private:
FILE* m_temperatureFiles[static_cast<int>(ThermalSensorType::Count)];
};
#endif // !defined(AZ_RELEASE_BUILD)
@@ -36,4 +36,6 @@ set(FILES
AzFramework/Process/ProcessCommon.h
AzFramework/Process/ProcessWatcher_Android.cpp
AzFramework/Process/ProcessCommunicator_Android.cpp
AzFramework/Thermal/ThermalInfo_Android.cpp
AzFramework/Thermal/ThermalInfo_Android.h
)
@@ -57,13 +57,16 @@ namespace AzGameFramework
AZStd::vector<char> scratchBuffer;
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_TargetBuildDependencyRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, true);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
#endif
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_TargetBuildDependencyRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
// Used the lowercase the platform name since the bootstrap.game.<config>.<platform>.setreg is being loaded
// from the asset cache root where all the files are in lowercased from regardless of the filesystem case-sensitivity
static constexpr char filename[] = "bootstrap.game." AZ_BUILD_CONFIGURATION_TYPE "." AZ_TRAIT_OS_PLATFORM_CODENAME_LOWER ".setreg";
@@ -77,6 +80,7 @@ namespace AzGameFramework
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, true);
#endif
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="20px" viewBox="0 0 22 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icon / Toolbar / Play Console / Simulate Physics</title>
<defs>
<filter id="filter-1">
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 1.000000 0"></feColorMatrix>
</filter>
<path d="M15.6428742,11.9827626 C14.6770188,10.7687802 14.4956657,9.03975584 15.318317,7.61488202 C16.3923878,5.75453685 18.7712019,5.11713552 20.6315471,6.1912063 C22.4918923,7.26527709 23.1292936,9.64409122 22.0552228,11.5044364 C21.013572,13.3086286 18.7447543,13.9625898 16.9118187,13.0207075 C16.3649862,12.6909949 16.1306352,12.5503038 15.6428742,11.9827626 Z M21.8449657,10.7460039 C22.0595758,10.1342007 22.2373043,8.55906064 21.8449657,9.05833735 C21.4526271,9.55761407 21.2918858,9.96701497 20.8130083,10.4818331 C19.9042528,11.4587922 18.2692551,11.5405181 17.2685207,11.5405181 C16.2677863,11.5405181 17.2685207,12.7159692 18.7447536,12.7159692 C20.2209864,12.7159692 21.4894947,11.7593684 21.8449657,10.7460039 Z M16.197388,14.0090117 C16.3975463,13.8229455 16.701528,13.7946404 16.9039947,13.9824218 C17.1064613,14.1702033 17.0967404,14.4628636 16.9305845,14.6890285 C16.3625338,15.4622373 15.7176919,17.2794303 15.043117,20.0013354 L13.9731652,20.0013354 C13.74245,13.5862059 13.0475403,9.88991503 12.1237705,9.88991503 C11.4442663,9.88991503 10.8362307,11.6219 10.2533824,15.1128617 C10.1857467,15.5179652 9.53567637,19.2951617 9.47653253,20.0013354 L8.32801304,20.0013354 C8.32801304,11.4928776 6.37313749,5.95119082 2.36188186,4.12720757 C2.11050727,4.01290346 1.65264126,3.52516756 2.0367453,2.87964604 C2.42084934,2.23412452 3.17248781,2.63112648 3.42386239,2.7454306 C7.09988702,4.41697884 8.58435154,8.916133 9.2170288,15.2523916 C9.23745765,15.1271094 9.25478369,15.0215631 9.26703535,14.9481819 C9.97359325,10.7162629 10.1786779,8.42129315 12.1237705,8.42129315 C14.0688632,8.42129315 14.4491297,12.5275507 14.8251327,17.6881742 C15.0978485,16.4034552 15.5446067,14.6158341 16.197388,14.0090117 Z M2.0241711,20.6412764 L22.0595758,20.6412764 L20.6246231,21.7592943 L3.70859025,21.7592943 L2.0241711,20.6412764 Z" id="path-2"></path>
</defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Play-Console-v2" transform="translate(-169.000000, -8.000000)">
<g id="Play-Console" transform="translate(100.000000, 0.000000)">
<g id="Icon-/-Toolbar-/-Play-Console-/-Simulate-Physics" transform="translate(68.000000, 6.000000)" filter="url(#filter-1)">
<g>
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<use id="Shape" fill="#FFFFFF" fill-rule="nonzero" xlink:href="#path-2"></use>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -372,6 +372,7 @@
<file>img/UI20/toolbar/Select.svg</file>
<file>img/UI20/toolbar/select_object.svg</file>
<file>img/UI20/toolbar/Select_terrain.svg</file>
<file>img/UI20/toolbar/Simulate_Physics.svg</file>
<file>img/UI20/toolbar/Simulate_Physics_on_selected_objects.svg</file>
<file>img/UI20/toolbar/Terrain.svg</file>
<file>img/UI20/toolbar/Terrain_Texture.svg</file>
+2 -3
View File
@@ -17,7 +17,7 @@
#include <array>
AZ_PUSH_DISABLE_WARNING(4389 4800, "-Wunknown-warning-option"); // 'int' : forcing value to bool 'true' or 'false' (performance warning).
#undef strdup // platform.h in CryCommon changes this define which is required by googletest
#undef strdup // This define is required by googletest
#include <gtest/gtest.h>
#include <gmock/gmock.h>
AZ_POP_DISABLE_WARNING;
@@ -477,8 +477,7 @@ int main(int argc, char** argv)
} \
} while (0); // safe multi-line macro - creates a single statement
// Avoid accidentally being managed by CryMemory, or problems with new/delete when
// AZ allocators are not ready or properly un/initialized.
// Avoid problems with new/delete when AZ allocators are not ready or properly un/initialized.
#define AZ_TEST_CLASS_ALLOCATOR(Class_) \
void* operator new (size_t size) \
{ \
@@ -263,8 +263,6 @@ namespace AzToolsFramework
return SourceFileDetails("Icons/AssetBrowser/XML_16.svg");
}
// this is here to prevent having to include IResourceCompilerHelper, which is in CryCommon.
static const char* sourceFormats[] = { ".tif", ".bmp", ".gif", ".jpg", ".jpeg", ".jpe", ".tga", ".png" };
for (unsigned int sourceImageFormatIndex = 0, numSources = AZ_ARRAY_SIZE(sourceFormats); sourceImageFormatIndex < numSources; ++sourceImageFormatIndex)
@@ -52,6 +52,7 @@
#include <AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiSystemComponent.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerComponent.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerNullComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemComponent.h>
@@ -91,6 +92,7 @@ namespace AzToolsFramework
AzToolsFramework::AssetBundleComponent::CreateDescriptor(),
AzToolsFramework::SliceDependencyBrowserComponent::CreateDescriptor(),
AzToolsFramework::Thumbnailer::ThumbnailerComponent::CreateDescriptor(),
AzToolsFramework::Thumbnailer::ThumbnailerNullComponent::CreateDescriptor(),
AzToolsFramework::AssetBrowser::AssetBrowserComponent::CreateDescriptor(),
AzToolsFramework::EditorInteractionSystemComponent::CreateDescriptor(),
AzToolsFramework::Components::EditorComponentAPIComponent::CreateDescriptor(),
@@ -13,6 +13,7 @@
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Script/ScriptSystemBus.h>
#include <AzCore/Serialization/Utils.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzFramework/Spawnable/RootSpawnableInterface.h>
@@ -281,14 +282,6 @@ namespace AzToolsFramework
containerEntity->AddComponent(aznew Prefab::EditorPrefabComponent());
HandleEntitiesAdded({containerEntity});
HandleEntitiesAdded(entities);
// Update the template of the instance since we modified the entities of the instance by calling HandleEntitiesAdded.
Prefab::PrefabDom serializedInstance;
if (Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(addedInstance, serializedInstance))
{
m_prefabSystemComponent->UpdatePrefabTemplate(addedInstance.GetTemplateId(), serializedInstance);
}
return addedInstance;
}
@@ -347,6 +340,65 @@ namespace AzToolsFramework
m_validateEntitiesCallback = AZStd::move(validateEntitiesCallback);
}
void PrefabEditorEntityOwnershipService::LoadReferencedAssets(AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets)
{
// Start our loads on all assets by calling GetAsset from the AssetManager
for (AZ::Data::Asset<AZ::Data::AssetData>& asset : referencedAssets)
{
if (!asset.GetId().IsValid())
{
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
continue;
}
const AZ::Data::AssetLoadBehavior loadBehavior = asset.GetAutoLoadBehavior();
if (loadBehavior == AZ::Data::AssetLoadBehavior::NoLoad)
{
continue;
}
AZ::Data::AssetId assetId = asset.GetId();
AZ::Data::AssetType assetType = asset.GetType();
asset = AZ::Data::AssetManager::Instance().GetAsset(assetId, assetType, loadBehavior);
if (!asset.GetId().IsValid())
{
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
continue;
}
}
// For all Preload assets we block until they're ready
// We do this as a seperate pass so that we don't interrupt queuing up all other asset loads
for (AZ::Data::Asset<AZ::Data::AssetData>& asset : referencedAssets)
{
if (!asset.GetId().IsValid())
{
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
continue;
}
const AZ::Data::AssetLoadBehavior loadBehavior = asset.GetAutoLoadBehavior();
if (loadBehavior != AZ::Data::AssetLoadBehavior::PreLoad)
{
continue;
}
asset.BlockUntilLoadComplete();
if (asset.IsError())
{
AZ_Error("Prefab", false, "Asset with id %s failed to preload while entering game mode",
asset.GetId().ToString<AZStd::string>().c_str());
continue;
}
}
}
void PrefabEditorEntityOwnershipService::StartPlayInEditor()
{
// This is a workaround until the replacement for GameEntityContext is done
@@ -386,16 +438,21 @@ namespace AzToolsFramework
rootSpawnableIndex = m_playInEditorData.m_assets.size();
}
LoadReferencedAssets(product.GetReferencedAssets());
AZ::Data::AssetInfo info;
info.m_assetId = product.GetAsset().GetId();
info.m_assetType = product.GetAssetType();
info.m_relativePath = product.GetId();
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, product.GetAsset().GetId(), info);
&AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, info.m_assetId, info);
m_playInEditorData.m_assets.emplace_back(product.ReleaseAsset().release(), AZ::Data::AssetLoadBehavior::Default);
}
// make sure that PRE_NOTIFY assets get their notify before we activate, so that we can preserve the order of
// (load asset) -> (notify) -> (init) -> (activate)
AZ::Data::AssetManager::Instance().DispatchEvents();
if (rootSpawnableIndex != NoRootSpawnable)
{
@@ -201,6 +201,8 @@ namespace AzToolsFramework
void OnEntityRemoved(AZ::EntityId entityId);
void LoadReferencedAssets(AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets);
OnEntitiesAddedCallback m_entitiesAddedCallback;
OnEntitiesRemovedCallback m_entitiesRemovedCallback;
ValidateEntitiesCallback m_validateEntitiesCallback;
@@ -270,7 +270,7 @@ namespace AzToolsFramework
return parentInstance;
}
void InstanceToTemplatePropagator::AddPatchesToLink(PrefabDom& patches, Link& link)
void InstanceToTemplatePropagator::AddPatchesToLink(const PrefabDom& patches, Link& link)
{
PrefabDom& linkDom = link.GetLinkDom();
PrefabDomValueReference linkPatchesReference =
@@ -279,7 +279,14 @@ namespace AzToolsFramework
// This logic only covers addition of patches. If patches already exists, the given list of patches must be appended to them.
if (!linkPatchesReference.has_value())
{
linkDom.AddMember(rapidjson::StringRef(PrefabDomUtils::PatchesName), patches, linkDom.GetAllocator());
/*
If the original allocator the patches were created with gets destroyed, then the patches would become garbage in the
linkDom. Since we cannot guarantee the lifecycle of the patch allocators, we are doing a copy of the patches here to
associate them with the linkDom's allocator.
*/
PrefabDom patchesCopy;
patchesCopy.CopyFrom(patches, linkDom.GetAllocator());
linkDom.AddMember(rapidjson::StringRef(PrefabDomUtils::PatchesName), patchesCopy, linkDom.GetAllocator());
}
}
}
@@ -41,7 +41,7 @@ namespace AzToolsFramework
void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) override;
void AddPatchesToLink(PrefabDom& patches, Link& link);
void AddPatchesToLink(const PrefabDom& patches, Link& link);
private:
@@ -27,6 +27,7 @@ namespace AzToolsFramework
using PrefabDomList = AZStd::vector<PrefabDom>;
using PrefabDomReference = AZStd::optional<AZStd::reference_wrapper<PrefabDom>>;
using PrefabDomConstReference = AZStd::optional<AZStd::reference_wrapper<const PrefabDom>>;
using PrefabDomValueReference = AZStd::optional<AZStd::reference_wrapper<PrefabDomValue>>;
using PrefabDomValueConstReference = AZStd::optional<AZStd::reference_wrapper<const PrefabDomValue>>;
@@ -11,8 +11,10 @@
*/
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetJsonSerializer.h>
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
@@ -115,6 +117,48 @@ namespace AzToolsFramework
return true;
}
bool LoadInstanceFromPrefabDom(
Instance& instance, const PrefabDom& prefabDom, AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets, LoadInstanceFlags flags)
{
// When entities are rebuilt they are first destroyed. As a result any assets they were exclusively holding on to will
// be released and reloaded once the entities are built up again. By suspending asset release temporarily the asset reload
// is avoided.
AZ::Data::AssetManager::Instance().SuspendAssetRelease();
InstanceEntityIdMapper entityIdMapper;
entityIdMapper.SetLoadingInstance(instance);
if ((flags & LoadInstanceFlags::AssignRandomEntityId) == LoadInstanceFlags::AssignRandomEntityId)
{
entityIdMapper.SetEntityIdGenerationApproach(InstanceEntityIdMapper::EntityIdGenerationApproach::Random);
}
AZ::JsonDeserializerSettings settings;
// The InstanceEntityIdMapper is registered twice because it's used in several places during deserialization where one is
// specific for the InstanceEntityIdMapper and once for the generic JsonEntityIdMapper. Because the Json Serializer's meta
// data has strict typing and doesn't look for inheritance both have to be explicitly added so they're found both locations.
settings.m_metadata.Add(static_cast<AZ::JsonEntityIdSerializer::JsonEntityIdMapper*>(&entityIdMapper));
settings.m_metadata.Add(&entityIdMapper);
settings.m_metadata.Create<AZ::Data::SerializedAssetTracker>();
AZ::JsonSerializationResult::ResultCode result =
AZ::JsonSerialization::Load(instance, prefabDom, settings);
AZ::Data::AssetManager::Instance().ResumeAssetRelease();
if (result.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
{
AZ_Error("Prefab", false,
"Failed to de-serialize Prefab Instance from Prefab DOM. "
"Unable to proceed.");
return false;
}
AZ::Data::SerializedAssetTracker* assetTracker = settings.m_metadata.Find<AZ::Data::SerializedAssetTracker>();
referencedAssets = AZStd::move(assetTracker->GetTrackedAssets());
return true;
}
bool LoadInstanceFromPrefabDom(
Instance& instance, Instance::EntityList& newlyAddedEntities, const PrefabDom& prefabDom, LoadInstanceFlags flags)
{
@@ -155,6 +199,43 @@ namespace AzToolsFramework
return true;
}
void GetTemplateSourcePaths(const PrefabDomValue& prefabDom, AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths)
{
PrefabDomValueConstReference findSourceResult = PrefabDomUtils::FindPrefabDomValue(prefabDom, PrefabDomUtils::SourceName);
if (!findSourceResult.has_value() || !(findSourceResult->get().IsString()) ||
findSourceResult->get().GetStringLength() == 0)
{
AZ_Assert(
false,
"PrefabDomUtils::GetDependentTemplatePath - Source value of prefab in the provided DOM is not a valid string.");
return;
}
templateSourcePaths.emplace(findSourceResult->get().GetString());
PrefabDomValueConstReference instancesReference = GetInstancesValue(prefabDom);
if (instancesReference.has_value())
{
const PrefabDomValue& instances = instancesReference->get();
for (PrefabDomValue::ConstMemberIterator instanceIterator = instances.MemberBegin();
instanceIterator != instances.MemberEnd(); ++instanceIterator)
{
GetTemplateSourcePaths(instanceIterator->value, templateSourcePaths);
}
}
}
PrefabDomValueConstReference GetInstancesValue(const PrefabDomValue& prefabDom)
{
PrefabDomValueConstReference findInstancesResult = FindPrefabDomValue(prefabDom, PrefabDomUtils::InstancesName);
if (!findInstancesResult.has_value() || !(findInstancesResult->get().IsObject()))
{
return AZStd::nullopt;
}
return findInstancesResult->get();
}
void PrintPrefabDomValue(
[[maybe_unused]] const AZStd::string_view printMessage,
[[maybe_unused]] const PrefabDomValue& prefabDomValue)
@@ -13,6 +13,7 @@
#pragma once
#include <AzCore/std/optional.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
@@ -42,7 +43,7 @@ namespace AzToolsFramework
/**
* Stores a valid Prefab Instance within a Prefab Dom. Useful for generating Templates
* @param instance The instance to store
* @param prefabDom the prefabDom that will be used to store the Instance data
* @param prefabDom The prefabDom that will be used to store the Instance data
* @return bool on whether the operation succeeded
*/
bool StoreInstanceInPrefabDom(const Instance& instance, PrefabDom& prefabDom);
@@ -60,20 +61,32 @@ namespace AzToolsFramework
/**
* Loads a valid Prefab Instance from a Prefab Dom. Useful for generating Instances.
* @param instance The Instance to load.
* @param prefabDom the prefabDom that will be used to load the Instance data.
* @param shouldClearContainers whether to clear containers in Instance while loading.
* @param prefabDom The prefabDom that will be used to load the Instance data.
* @param shouldClearContainers Whether to clear containers in Instance while loading.
* @return bool on whether the operation succeeded.
*/
bool LoadInstanceFromPrefabDom(
Instance& instance, const PrefabDom& prefabDom, LoadInstanceFlags flags = LoadInstanceFlags::None);
/**
* Loads a valid Prefab Instance from a Prefab Dom. Useful for generating Instances.
* @param instance The Instance to load.
* @param referencedAssets AZ::Assets discovered during json load are added to this list
* @param prefabDom The prefabDom that will be used to load the Instance data.
* @param shouldClearContainers Whether to clear containers in Instance while loading.
* @return bool on whether the operation succeeded.
*/
bool LoadInstanceFromPrefabDom(
Instance& instance, const PrefabDom& prefabDom, AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets,
LoadInstanceFlags flags = LoadInstanceFlags::None);
/**
* Loads a valid Prefab Instance from a Prefab Dom. Useful for generating Instances.
* @param instance The Instance to load.
* @param newlyAddedEntities The new instances added during deserializing the instance. These are the entities found
* in the prefabDom.
* @param prefabDom the prefabDom that will be used to load the Instance data.
* @param shouldClearContainers whether to clear containers in Instance while loading.
* @param prefabDom The prefabDom that will be used to load the Instance data.
* @param shouldClearContainers Whether to clear containers in Instance while loading.
* @return bool on whether the operation succeeded.
*/
bool LoadInstanceFromPrefabDom(
@@ -87,6 +100,20 @@ namespace AzToolsFramework
.Append(instanceName);
};
/**
* Gets a set of all the template source paths in the given dom.
* @param prefabDom The DOM to get the template source paths from.
* @param[out] templateSourcePaths The set of template source paths to populate.
*/
void GetTemplateSourcePaths(const PrefabDomValue& prefabDom, AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths);
/**
* Gets the instances DOM value from the given prefab DOM.
*
* @return the instances DOM value or AZStd::nullopt if it instances can't be found.
*/
PrefabDomValueConstReference GetInstancesValue(const PrefabDomValue& prefabDom);
/**
* Prints the contents of the given prefab DOM value to the debug output console in a readable format.
* @param printMessage The message that will be printed before printing the PrefabDomValue
@@ -89,7 +89,7 @@ namespace AzToolsFramework
if (!RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonRootEntityOwningInstance->get(), entities, instances))
{
return AZ::Failure(
AZStd::string("Could not create a new prefab out of the entities provided - entities do not share a common root."));
AZStd::string("Could not create a new prefab out of the entities provided - invalid selection."));
}
// When we create a prefab with other prefab instances, we have to remove the existing links between the source and
@@ -122,27 +122,50 @@ namespace AzToolsFramework
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
// Parent the entities to the container entity. Parenting the container entities of the instances passed to createPrefab
// will be done during the creation of links below.
for (AZ::Entity* topLevelEntity : entities)
{
AZ::TransformBus::Event(topLevelEntity->GetId(), &AZ::TransformBus::Events::SetParent, containerEntityId);
}
// Update the template of the instance since the entities are modified since the template creation.
Prefab::PrefabDom serializedInstance;
if (Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(instanceToCreate->get(), serializedInstance))
{
m_prefabSystemComponentInterface->UpdatePrefabTemplate(instanceToCreate->get().GetTemplateId(), serializedInstance);
}
instanceToCreate->get().GetNestedInstances([&](AZStd::unique_ptr<Instance>& nestedInstance) {
AZ_Assert(nestedInstance, "Invalid nested instance found in the new prefab created.");
EntityOptionalReference nestedInstanceContainerEntity = nestedInstance->GetContainerEntity();
AZ_Assert(
nestedInstanceContainerEntity, "Invalid container entity found for the nested instance used in prefab creation.");
// These link creations shouldn't be undone because that would put the template in a non-usable state if a user
// chooses to instantiate the template after undoing the creation.
CreateLink(
{&nestedInstanceContainerEntity->get()}, *nestedInstance, instanceToCreate->get().GetTemplateId(),
undoBatch.GetUndoBatch(), containerEntityId);
undoBatch.GetUndoBatch(), containerEntityId, false);
});
// Create a link between the templates of the newly created instance and the instance it's being parented under.
CreateLink(
topLevelEntities, instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(),
commonRootEntityId);
topLevelEntities, instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(),
undoBatch.GetUndoBatch(), commonRootEntityId);
// Change top level entities to be parented to the container entity
// Mark them as dirty so this change is correctly applied to the template
for (AZ::Entity* topLevelEntity : topLevelEntities)
{
m_prefabUndoCache.UpdateCache(topLevelEntity->GetId());
undoBatch.MarkEntityDirty(topLevelEntity->GetId());
AZ::TransformBus::Event(topLevelEntity->GetId(), &AZ::TransformBus::Events::SetParent, containerEntityId);
AZ::EntityId topLevelEntityId = topLevelEntity->GetId();
if (topLevelEntityId.IsValid())
{
m_prefabUndoCache.UpdateCache(topLevelEntity->GetId());
// Parenting entities would mark entities as dirty. But we want to unmark the top level entities as dirty because
// if we don't, the template created would be updated and cause issues with undo operation followed by instantiation.
ToolsApplicationRequests::Bus::Broadcast(
&ToolsApplicationRequests::Bus::Events::RemoveDirtyEntity, topLevelEntity->GetId());
}
}
// Select Container Entity
@@ -187,25 +210,30 @@ namespace AzToolsFramework
auto relativePath = m_prefabLoaderInterface->GetRelativePathToProject(filePath);
Prefab::TemplateId templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath);
// If the template isn't currently loaded, there's no way for it to be in the hierarchy so we just skip the check.
if (templateId != Prefab::InvalidTemplateId && IsPrefabInInstanceAncestorHierarchy(templateId, instanceToParentUnder->get()))
if (templateId == InvalidTemplateId)
{
return AZ::Failure(
AZStd::string::format(
"Instantiate Prefab operation aborted - Cyclical dependency detected\n(%s depends on %s).",
relativePath.Native().c_str(),
instanceToParentUnder->get().GetTemplateSourcePath().Native().c_str()
)
);
// Load the template from the file
templateId = m_prefabLoaderInterface->LoadTemplateFromFile(filePath);
AZ_Assert(templateId != InvalidTemplateId, "Template with source path %s couldn't be loaded correctly.", filePath);
}
const PrefabDom& templateDom = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
AZStd::unordered_set<AZ::IO::Path> templatePaths;
PrefabDomUtils::GetTemplateSourcePaths(templateDom, templatePaths);
if (IsCyclicalDependencyFound(instanceToParentUnder->get(), templatePaths))
{
return AZ::Failure(AZStd::string::format(
"Instantiate Prefab operation aborted - Cyclical dependency detected\n(%s depends on %s).",
relativePath.Native().c_str(), instanceToParentUnder->get().GetTemplateSourcePath().Native().c_str()));
}
{
// Initialize Undo Batch object
ScopedUndoBatch undoBatch("Instantiate Prefab");
PrefabDom instanceToParentUnderDomBeforeCreate;
m_instanceToTemplateInterface->GenerateDomForInstance(
instanceToParentUnderDomBeforeCreate, instanceToParentUnder->get());
m_instanceToTemplateInterface->GenerateDomForInstance(instanceToParentUnderDomBeforeCreate, instanceToParentUnder->get());
// Instantiate the Prefab
auto instanceToCreate = prefabEditorEntityOwnershipInterface->InstantiatePrefab(relativePath, instanceToParentUnder);
@@ -219,8 +247,7 @@ namespace AzToolsFramework
PrefabUndoHelpers::UpdatePrefabInstance(
instanceToParentUnder->get(), "Update prefab instance", instanceToParentUnderDomBeforeCreate, undoBatch.GetUndoBatch());
CreateLink({}, instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(),
undoBatch.GetUndoBatch(), parent);
CreateLink({}, instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), parent);
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
// Apply position
@@ -237,6 +264,21 @@ namespace AzToolsFramework
// Retrieve entityList from entityIds
inputEntityList = EntityIdListToEntityList(entityIds);
// Remove Level Container Entity if it's part of the list
AZ::EntityId levelEntityId = GetLevelInstanceContainerEntityId();
if (levelEntityId.IsValid())
{
AZ::Entity* levelEntity = GetEntityById(levelEntityId);
if (levelEntity)
{
auto levelEntityIter = AZStd::find(inputEntityList.begin(), inputEntityList.end(), levelEntity);
if (levelEntityIter != inputEntityList.end())
{
inputEntityList.erase(levelEntityIter);
}
}
}
// Find common root and top level entities
bool entitiesHaveCommonRoot = false;
@@ -258,17 +300,17 @@ namespace AzToolsFramework
return AZ::Success();
}
bool PrefabPublicHandler::IsPrefabInInstanceAncestorHierarchy(TemplateId prefabTemplateId, InstanceOptionalConstReference instance)
bool PrefabPublicHandler::IsCyclicalDependencyFound(
InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths)
{
InstanceOptionalConstReference currentInstance = instance;
while (currentInstance.has_value())
{
if (currentInstance->get().GetTemplateId() == prefabTemplateId)
if (templateSourcePaths.contains(currentInstance->get().GetTemplateSourcePath()))
{
return true;
}
currentInstance = currentInstance->get().GetParentInstance();
}
@@ -277,7 +319,7 @@ namespace AzToolsFramework
void PrefabPublicHandler::CreateLink(
const EntityList& topLevelEntities, Instance& sourceInstance, TemplateId targetTemplateId,
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId commonRootEntityId)
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId commonRootEntityId, const bool isUndoRedoSupportNeeded)
{
AZ::EntityId containerEntityId = sourceInstance.GetContainerEntityId();
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
@@ -303,9 +345,19 @@ namespace AzToolsFramework
m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter);
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
LinkId linkId = PrefabUndoHelpers::CreateLink(
sourceInstance.GetTemplateId(), targetTemplateId, patch, sourceInstance.GetInstanceAlias(),
undoBatch);
LinkId linkId;
if (isUndoRedoSupportNeeded)
{
linkId = PrefabUndoHelpers::CreateLink(
sourceInstance.GetTemplateId(), targetTemplateId, AZStd::move(patch), sourceInstance.GetInstanceAlias(), undoBatch);
}
else
{
linkId = m_prefabSystemComponentInterface->CreateLink(
targetTemplateId, sourceInstance.GetTemplateId(), sourceInstance.GetInstanceAlias(), patch,
InvalidLinkId);
m_prefabSystemComponentInterface->PropagateTemplateChanges(targetTemplateId);
}
sourceInstance.SetLinkId(linkId);
@@ -338,7 +390,7 @@ namespace AzToolsFramework
patchesCopyForUndoSupport.CopyFrom(nestedInstanceLinkPatches->get(), patchesCopyForUndoSupport.GetAllocator());
PrefabUndoHelpers::RemoveLink(
sourceInstance->GetTemplateId(), targetTemplateId, sourceInstance->GetInstanceAlias(), sourceInstance->GetLinkId(),
patchesCopyForUndoSupport, undoBatch);
AZStd::move(patchesCopyForUndoSupport), undoBatch);
}
PrefabOperationResult PrefabPublicHandler::SavePrefab(AZ::IO::Path filePath)
@@ -588,11 +640,18 @@ namespace AzToolsFramework
if (!EntitiesBelongToSameInstance(entityIds))
{
return AZ::Failure(AZStd::string("DeleteEntitiesAndAllDescendantsInInstance - Deletion Error. Cannot delete multiple "
"entities belonging to different instances with one operation."));
return AZ::Failure(AZStd::string("Cannot delete multiple entities belonging to different instances with one operation."));
}
InstanceOptionalReference instance = GetOwnerInstanceByEntityId(entityIds[0]);
AZ::EntityId firstEntityIdToDelete = entityIds[0];
InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDelete);
// If the first entity id is a container entity id, then we need to mark its parent as the common owning instance because you
// cannot delete an instance from itself.
if (commonOwningInstance->get().GetContainerEntityId() == firstEntityIdToDelete)
{
commonOwningInstance = commonOwningInstance->get().GetParentInstance();
}
// Retrieve entityList from entityIds
EntityList inputEntityList = EntityIdListToEntityList(entityIds);
@@ -632,14 +691,14 @@ namespace AzToolsFramework
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DeleteEntities:UndoCaptureAndPurgeEntities");
Prefab::PrefabDom instanceDomBefore;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, instance->get());
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get());
if (deleteDescendants)
{
AZStd::vector<AZ::Entity*> entities;
AZStd::vector<AZStd::unique_ptr<Instance>> instances;
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, instance->get(), entities, instances);
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
if (!success)
{
@@ -653,6 +712,7 @@ namespace AzToolsFramework
for (auto& nestedInstance : instances)
{
RemoveLink(nestedInstance, commonOwningInstance->get().GetTemplateId(), currentUndoBatch);
nestedInstance.reset();
}
}
@@ -664,22 +724,22 @@ namespace AzToolsFramework
// If this is the container entity, it actually represents the instance so get its owner
if (owningInstance->get().GetContainerEntityId() == entityId)
{
auto instancePtr = instance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
instancePtr.reset();
auto instancePtr = commonOwningInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), currentUndoBatch);
}
else
{
instance->get().DetachEntity(entityId);
commonOwningInstance->get().DetachEntity(entityId);
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entityId);
}
}
}
Prefab::PrefabDom instanceDomAfter;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, instance->get());
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, commonOwningInstance->get());
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance deletion");
command->Capture(instanceDomBefore, instanceDomAfter, instance->get().GetTemplateId());
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
command->SetParent(selCommand);
}
@@ -807,6 +867,11 @@ namespace AzToolsFramework
const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
EntityList& outEntities, AZStd::vector<AZStd::unique_ptr<Instance>>& outInstances) const
{
if (inputEntities.size() == 0)
{
return false;
}
AZStd::queue<AZ::Entity*> entityQueue;
for (auto inputEntity : inputEntities)
@@ -894,7 +959,7 @@ namespace AzToolsFramework
outInstances.push_back(AZStd::move(commonRootEntityOwningInstance.DetachNestedInstance(instancePtr->GetInstanceAlias())));
}
return true;
return (outEntities.size() + outInstances.size()) > 0;
}
bool PrefabPublicHandler::EntitiesBelongToSameInstance(const EntityIdList& entityIds) const
@@ -942,5 +1007,5 @@ namespace AzToolsFramework
return true;
}
}
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -12,8 +12,8 @@
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
@@ -77,10 +77,11 @@ namespace AzToolsFramework
* \param targetInstance The id of the target template.
* \param undoBatch The undo batch to set as parent for this create link action.
* \param commonRootEntityId The id of the entity that the source instance should be parented under.
* \param isUndoRedoSupportNeeded The flag indicating whether the link should be created with undo/redo support or not.
*/
void CreateLink(
const EntityList& topLevelEntities, Instance& sourceInstance, TemplateId targetTemplateId,
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId commonRootEntityId);
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId commonRootEntityId, const bool isUndoRedoSupportNeeded = true);
/**
* Removes the link between template of the sourceInstance and the template corresponding to targetTemplateId.
@@ -106,13 +107,15 @@ namespace AzToolsFramework
const AZStd::vector<AZ::EntityId>& entityIds, EntityList& inputEntityList, EntityList& topLevelEntities,
AZ::EntityId& commonRootEntityId, InstanceOptionalReference& commonRootEntityOwningInstance);
/* Detects whether an instance of prefabTemplateId is present in the hierarchy of ancestors of instance.
/* Checks whether the template source path of any of the ancestors in the instance hierarchy matches with one of the
* paths provided in a set.
*
* \param prefabTemplateId The template id to test for
* \param instance The instance whose ancestor hierarchy prefabTemplateId will be tested against.
* \return true if an instance of the template of id prefabTemplateId could be found in the ancestor hierarchy of instance, false otherwise.
* \param instance The instance whose ancestor hierarchy the provided set of template source paths will be tested against.
* \param templateSourcePaths The template source paths provided to be checked against the instance ancestor hierarchy.
* \return true if any of the template source paths could be found in the ancestor hierarchy of instance, false otherwise.
*/
bool IsPrefabInInstanceAncestorHierarchy(TemplateId prefabTemplateId, InstanceOptionalConstReference instance);
bool IsCyclicalDependencyFound(
InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths);
static Instance* GetParentInstance(Instance* instance);
static Instance* GetAncestorOfInstanceThatIsChildOfRoot(const Instance* ancestor, Instance* descendant);
@@ -128,5 +131,5 @@ namespace AzToolsFramework
uint64_t m_newEntityCounter = 1;
};
}
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -583,7 +583,7 @@ namespace AzToolsFramework
const TemplateId& linkTargetId,
const TemplateId& linkSourceId,
const InstanceAlias& instanceAlias,
const PrefabDomReference linkPatch,
const PrefabDomConstReference linkPatches,
const LinkId& linkId)
{
if (linkTargetId == InvalidTemplateId)
@@ -667,9 +667,9 @@ namespace AzToolsFramework
rapidjson::StringRef(PrefabDomUtils::SourceName), rapidjson::StringRef(sourceTemplate.GetFilePath().c_str()),
newLink.GetLinkDom().GetAllocator());
if (linkPatch && linkPatch->get().IsArray() && !(linkPatch->get().Empty()))
if (linkPatches && linkPatches->get().IsArray() && !(linkPatches->get().Empty()))
{
m_instanceToTemplatePropagator.AddPatchesToLink(linkPatch.value(), newLink);
m_instanceToTemplatePropagator.AddPatchesToLink(linkPatches.value(), newLink);
}
//update the target template dom to have the proper values for the source template dom
@@ -156,7 +156,7 @@ namespace AzToolsFramework
const TemplateId& linkTargetId,
const TemplateId& linkSourceId,
const InstanceAlias& instanceAlias,
const PrefabDomReference linkPatch,
const PrefabDomConstReference linkPatches,
const LinkId& linkId = InvalidLinkId) override;
/**
@@ -43,9 +43,9 @@ namespace AzToolsFramework
PrefabDomValue::MemberIterator& instanceIterator, InstanceOptionalReference instance) = 0;
//creates a new Link
virtual LinkId CreateLink(const TemplateId& linkTargetId, const TemplateId& linkSourceId,
const InstanceAlias& instanceAlias, const PrefabDomReference linkPatch,
const LinkId& linkId = InvalidLinkId) = 0;
virtual LinkId CreateLink(
const TemplateId& linkTargetId, const TemplateId& linkSourceId, const InstanceAlias& instanceAlias,
const PrefabDomConstReference linkPatches, const LinkId& linkId = InvalidLinkId) = 0;
virtual void RemoveLink(const LinkId& linkId) = 0;
@@ -124,7 +124,7 @@ namespace AzToolsFramework
const TemplateId& targetId,
const TemplateId& sourceId,
const InstanceAlias& instanceAlias,
PrefabDomReference linkPatches,
PrefabDom linkPatches,
const LinkId linkId)
{
m_targetId = targetId;
@@ -132,10 +132,7 @@ namespace AzToolsFramework
m_instanceAlias = instanceAlias;
m_linkId = linkId;
if (linkPatches.has_value())
{
m_linkPatches = AZStd::move(linkPatches->get());
}
m_linkPatches = AZStd::move(linkPatches);
//if linkId is invalid, set as ADD
if (m_linkId == InvalidLinkId)
@@ -228,7 +225,7 @@ namespace AzToolsFramework
if (link.has_value())
{
m_linkDomPrevious = AZStd::move(link->get().GetLinkDom());
m_linkDomPrevious.CopyFrom(link->get().GetLinkDom(), m_linkDomPrevious.GetAllocator());
}
//get source templateDom
@@ -275,7 +272,7 @@ namespace AzToolsFramework
if (patchesIter == m_linkDomNext.MemberEnd())
{
m_linkDomNext.AddMember(
rapidjson::GenericStringRef(PrefabDomUtils::PatchesName), patchLinkCopy, m_linkDomNext.GetAllocator());
rapidjson::GenericStringRef(PrefabDomUtils::PatchesName), AZStd::move(patchLinkCopy), m_linkDomNext.GetAllocator());
}
else
{
@@ -303,9 +300,7 @@ namespace AzToolsFramework
return;
}
PrefabDom moveLink;
moveLink.CopyFrom(linkDom, linkDom.GetAllocator());
link->get().GetLinkDom() = AZStd::move(moveLink);
link->get().SetLinkDom(linkDom);
//propagate the link changes
link->get().UpdateTarget();
@@ -101,7 +101,7 @@ namespace AzToolsFramework
const TemplateId& targetId,
const TemplateId& sourceId,
const InstanceAlias& instanceAlias,
PrefabDomReference linkPatches = PrefabDomReference(),
PrefabDom linkPatches = PrefabDom(),
const LinkId linkId = InvalidLinkId);
void Undo() override;
@@ -34,11 +34,11 @@ namespace AzToolsFramework
}
LinkId CreateLink(
TemplateId sourceTemplateId, TemplateId targetTemplateId, PrefabDomReference patch,
TemplateId sourceTemplateId, TemplateId targetTemplateId, PrefabDom patch,
const InstanceAlias& instanceAlias, UndoSystem::URSequencePoint* undoBatch)
{
auto linkAddUndo = aznew PrefabUndoInstanceLink("Create Link");
linkAddUndo->Capture(targetTemplateId, sourceTemplateId, instanceAlias, patch, InvalidLinkId);
linkAddUndo->Capture(targetTemplateId, sourceTemplateId, instanceAlias, AZStd::move(patch), InvalidLinkId);
linkAddUndo->SetParent(undoBatch);
linkAddUndo->Redo();
@@ -47,10 +47,10 @@ namespace AzToolsFramework
void RemoveLink(
TemplateId sourceTemplateId, TemplateId targetTemplateId, const InstanceAlias& instanceAlias, LinkId linkId,
PrefabDomReference linkPatches, UndoSystem::URSequencePoint* undoBatch)
PrefabDom linkPatches, UndoSystem::URSequencePoint* undoBatch)
{
auto linkRemoveUndo = aznew PrefabUndoInstanceLink("Remove Link");
linkRemoveUndo->Capture(targetTemplateId, sourceTemplateId, instanceAlias, linkPatches, linkId);
linkRemoveUndo->Capture(targetTemplateId, sourceTemplateId, instanceAlias, AZStd::move(linkPatches), linkId);
linkRemoveUndo->SetParent(undoBatch);
linkRemoveUndo->Redo();
}
@@ -22,11 +22,11 @@ namespace AzToolsFramework
const Instance& instance, AZStd::string_view undoMessage, const PrefabDom& instanceDomBeforeUpdate,
UndoSystem::URSequencePoint* undoBatch);
LinkId CreateLink(
TemplateId sourceTemplateId, TemplateId targetTemplateId, PrefabDomReference patch,
TemplateId sourceTemplateId, TemplateId targetTemplateId, PrefabDom patch,
const InstanceAlias& instanceAlias, UndoSystem::URSequencePoint* undoBatch);
void RemoveLink(
TemplateId sourceTemplateId, TemplateId targetTemplateId, const InstanceAlias& instanceAlias, LinkId linkId,
PrefabDomReference linkPatches, UndoSystem::URSequencePoint* undoBatch);
PrefabDom linkPatches, UndoSystem::URSequencePoint* undoBatch);
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -63,7 +63,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
AZStd::move(uniqueName), context.GetSourceUuid(), AZStd::move(serializer));
AZ_Assert(spawnable, "Failed to create a new spawnable.");
bool result = SpawnableUtils::CreateSpawnable(*spawnable, prefab);
bool result = SpawnableUtils::CreateSpawnable(*spawnable, prefab, object.GetReferencedAssets());
if (result)
{
AzFramework::Spawnable::EntityList& entities = spawnable->GetEntities();
@@ -84,8 +84,6 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
}
SpawnableUtils::SortEntitiesByTransformHierarchy(*spawnable);
context.GetProcessedObjects().push_back(AZStd::move(object));
context.RemovePrefab(prefabName);
}
else
{
@@ -10,6 +10,8 @@
*
*/
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -24,37 +26,14 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
return result.second;
}
bool PrefabProcessorContext::RemovePrefab(AZStd::string_view prefabName)
{
if (!m_isIterating)
{
return m_prefabs.erase(prefabName) > 0;
}
else
{
m_delayedDelete.emplace_back(prefabName);
}
return false;
}
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(AZStd::string_view, PrefabDom&)>& callback)
{
m_isIterating = true;
for (auto& it : m_prefabs)
{
if (AZStd::find(m_delayedDelete.begin(), m_delayedDelete.end(), it.first) == m_delayedDelete.end())
{
callback(it.first, it.second);
}
callback(it.first, it.second);
}
m_isIterating = false;
// Clear out any prefabs that have been deleted.
for (AZStd::string& deleted : m_delayedDelete)
{
m_prefabs.erase(deleted);
}
m_delayedDelete.clear();
}
void PrefabProcessorContext::ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDom&)>& callback) const
@@ -70,6 +49,44 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
return !m_prefabs.empty();
}
bool PrefabProcessorContext::RegisterSpawnableProductAssetDependency(AZStd::string prefabName, AZStd::string dependentPrefabName)
{
using ConversionUtils = PrefabConversionUtils::ProcessedObjectStore;
prefabName += AzFramework::Spawnable::DotFileExtension;
uint32_t spawnableSubId = ConversionUtils::BuildSubId(AZStd::move(prefabName));
dependentPrefabName += AzFramework::Spawnable::DotFileExtension;
uint32_t spawnablePrefabSubId = ConversionUtils::BuildSubId(AZStd::move(dependentPrefabName));
return RegisterSpawnableProductAssetDependency(spawnableSubId, spawnablePrefabSubId);
}
bool PrefabProcessorContext::RegisterSpawnableProductAssetDependency(AZStd::string prefabName, const AZ::Data::AssetId& dependentAssetId)
{
using ConversionUtils = PrefabConversionUtils::ProcessedObjectStore;
prefabName += AzFramework::Spawnable::DotFileExtension;
uint32_t spawnableSubId = ConversionUtils::BuildSubId(AZStd::move(prefabName));
AZ::Data::AssetId spawnableAssetId(GetSourceUuid(), spawnableSubId);
return RegisterProductAssetDependency(spawnableAssetId, dependentAssetId);
}
bool PrefabProcessorContext::RegisterSpawnableProductAssetDependency(uint32_t spawnableAssetSubId, uint32_t dependentSpawnableAssetSubId)
{
AZ::Data::AssetId spawnableAssetId(GetSourceUuid(), spawnableAssetSubId);
AZ::Data::AssetId dependentSpawnableAssetId(GetSourceUuid(), dependentSpawnableAssetSubId);
return RegisterProductAssetDependency(spawnableAssetId, dependentSpawnableAssetId);
}
bool PrefabProcessorContext::RegisterProductAssetDependency(const AZ::Data::AssetId& assetId, const AZ::Data::AssetId& dependentAssetId)
{
return m_registeredProductAssetDependencies[assetId].emplace(dependentAssetId).second;
}
PrefabProcessorContext::ProcessedObjectStoreContainer& PrefabProcessorContext::GetProcessedObjects()
{
return m_products;
@@ -80,6 +97,16 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
return m_products;
}
PrefabProcessorContext::ProductAssetDependencyContainer& PrefabProcessorContext::GetRegisteredProductAssetDependencies()
{
return m_registeredProductAssetDependencies;
}
const PrefabProcessorContext::ProductAssetDependencyContainer& PrefabProcessorContext::GetRegisteredProductAssetDependencies() const
{
return m_registeredProductAssetDependencies;
}
void PrefabProcessorContext::SetPlatformTags(AZ::PlatformTagSet tags)
{
m_platformTags = AZStd::move(tags);

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