Merge branch 'main' into ly-as-sdk/LYN-2948

This commit is contained in:
phistere
2021-05-13 11:25:03 -05:00
1224 changed files with 18773 additions and 126359 deletions
-1
View File
@@ -1 +0,0 @@
*.xml
-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
{
@@ -1222,6 +1221,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
@@ -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>()
@@ -1304,7 +1304,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)
{
@@ -14,8 +14,6 @@
#include <limits>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Memory/SystemAllocator.h>
@@ -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
@@ -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:
/**
@@ -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>()
serializeContext->ClassDeprecate("NetBindable", "{80206665-D429-4703-B42E-94434F82F381}");
serializeContext->Class<TransformComponent, AZ::Component>()
->Version(4, &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
@@ -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,7 +978,7 @@ namespace AzFramework
return true;
};
serializeContext->Class<ScriptComponent, AZ::Component, NetBindable>()
serializeContext->Class<ScriptComponent, AZ::Component>()
->Version(3, converter)
->Field("ContextID", &ScriptComponent::m_contextId)
->Field("Properties", &ScriptComponent::m_properties)
@@ -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);
}
}
@@ -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>
@@ -156,35 +157,25 @@ namespace AzFramework
camera.m_lookAt = transform.GetTranslation() + (camera.Rotation().GetBasisY() * -camera.m_lookDist);
}
static ScreenVector CursorDelta(const AZStd::optional<ScreenPoint>& currentPosition, const AZStd::optional<ScreenPoint>& lastPosition)
{
return currentPosition.has_value() && lastPosition.has_value() ? currentPosition.value() - lastPosition.value()
: ScreenVector(0, 0);
}
bool CameraSystem::HandleEvents(const InputEvent& event)
{
if (const auto& cursor = AZStd::get_if<CursorEvent>(&event))
{
m_currentCursorPosition = cursor->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, CursorDelta(m_currentCursorPosition, m_lastCursorPosition), m_scrollDelta);
return m_cameras.HandleEvents(event, m_cursorState.CursorDelta(), m_scrollDelta);
}
Camera CameraSystem::StepCamera(const Camera& targetCamera, const float deltaTime)
{
const auto cursorDelta = CursorDelta(m_currentCursorPosition, m_lastCursorPosition);
if (m_currentCursorPosition.has_value())
{
m_lastCursorPosition = m_currentCursorPosition;
}
const auto nextCamera = m_cameras.StepCamera(targetCamera, m_cursorState.CursorDelta(), m_scrollDelta, deltaTime);
const auto nextCamera = m_cameras.StepCamera(targetCamera, cursorDelta, m_scrollDelta, deltaTime);
m_cursorState.Update();
m_scrollDelta = 0.0f;
@@ -236,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();)
{
@@ -275,34 +266,42 @@ namespace AzFramework
}
}
RotateCameraInput::RotateCameraInput(const InputChannelId rotateChannelId)
: m_rotateChannelId(rotateChannelId)
{
}
void RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
{
if (input->m_channelId == m_rotateChannelId)
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)
{
m_tryingToBegin = true;
m_moveAccumulator = 0.0f;
}
else if (input->m_state == InputChannel::State::Ended)
{
m_tryingToBegin = false;
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;
}();
if (m_tryingToBegin)
switch (const auto outcome = m_clickDetector.DetectClick(clickEvent, cursorDelta); outcome)
{
// only allow the action to begin if the mouse has been moved a small amount
m_moveAccumulator += ScreenVectorLength(cursorDelta);
if (m_moveAccumulator > ed_cameraSystemLookDeadzone)
{
BeginActivation();
m_tryingToBegin = false;
}
case ClickDetector::ClickOutcome::Move:
BeginActivation();
break;
case ClickDetector::ClickOutcome::Release:
EndActivation();
break;
default:
// noop
break;
}
}
@@ -324,6 +323,12 @@ namespace AzFramework
return nextCamera;
}
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)
{
@@ -400,6 +405,11 @@ namespace AzFramework
return TranslationType::Nil;
}
TranslateCameraInput::TranslateCameraInput(TranslationAxesFn translationAxesFn)
: m_translationAxesFn(AZStd::move(translationAxesFn))
{
}
void TranslateCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
@@ -407,11 +417,6 @@ namespace AzFramework
{
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)
{
@@ -579,6 +584,11 @@ namespace AzFramework
return nextCamera;
}
OrbitDollyCursorMoveCameraInput::OrbitDollyCursorMoveCameraInput(const InputChannelId dollyChannelId)
: m_dollyChannelId(dollyChannelId)
{
}
void OrbitDollyCursorMoveCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
@@ -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>
@@ -188,26 +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, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
InputChannelId m_rotateChannelId;
float m_moveAccumulator = 0.0f;
bool m_tryingToBegin = false;
ClickDetector m_clickDetector;
};
struct PanAxes
@@ -240,11 +237,8 @@ namespace AzFramework
class PanCameraInput : public CameraInput
{
public:
PanCameraInput(const InputChannelId panChannelId, PanAxesFn panAxesFn)
: m_panAxesFn(AZStd::move(panAxesFn))
, m_panChannelId(panChannelId)
{
}
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;
@@ -283,10 +277,8 @@ namespace AzFramework
class TranslateCameraInput : public CameraInput
{
public:
explicit TranslateCameraInput(TranslationAxesFn translationAxesFn)
: m_translationAxesFn(AZStd::move(translationAxesFn))
{
}
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;
@@ -363,8 +355,7 @@ namespace AzFramework
class OrbitDollyCursorMoveCameraInput : public CameraInput
{
public:
explicit OrbitDollyCursorMoveCameraInput(const InputChannelId dollyChannelId)
: m_dollyChannelId(dollyChannelId) {}
explicit OrbitDollyCursorMoveCameraInput(InputChannelId dollyChannelId);
void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) 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
@@ -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
@@ -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;
}
@@ -342,6 +335,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
@@ -381,16 +433,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)
{
@@ -199,6 +199,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)
{
@@ -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(
@@ -122,30 +122,49 @@ 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)
{
AZ::EntityId topLevelEntityId = topLevelEntity->GetId();
if (topLevelEntityId.IsValid())
{
m_prefabUndoCache.UpdateCache(topLevelEntityId);
undoBatch.MarkEntityDirty(topLevelEntityId);
AZ::TransformBus::Event(topLevelEntityId, &AZ::TransformBus::Events::SetParent, containerEntityId);
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());
}
}
@@ -296,7 +315,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);
@@ -322,9 +341,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);
@@ -357,7 +386,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)
@@ -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.
@@ -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);
@@ -29,6 +29,8 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
public:
using ProcessedObjectStoreContainer = AZStd::vector<ProcessedObjectStore>;
using ProductAssetDependencyContainer =
AZStd::unordered_map<AZ::Data::AssetId, AZStd::unordered_set<AZ::Data::AssetId>>;
AZ_CLASS_ALLOCATOR(PrefabProcessorContext, AZ::SystemAllocator, 0);
AZ_RTTI(PrefabProcessorContext, "{C7D77E3A-C544-486B-B774-7C82C38FE22F}");
@@ -37,14 +39,21 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
virtual ~PrefabProcessorContext() = default;
virtual bool AddPrefab(AZStd::string prefabName, PrefabDom prefab);
virtual bool RemovePrefab(AZStd::string_view prefabName);
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, PrefabDom&)>& callback);
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDom&)>& callback) const;
virtual bool HasPrefabs() const;
virtual bool RegisterSpawnableProductAssetDependency(AZStd::string prefabName, AZStd::string dependentPrefabName);
virtual bool RegisterSpawnableProductAssetDependency(AZStd::string prefabName, const AZ::Data::AssetId& dependentAssetId);
virtual bool RegisterSpawnableProductAssetDependency(uint32_t spawnableAssetSubId, uint32_t dependentSpawnableAssetSubId);
virtual bool RegisterProductAssetDependency(const AZ::Data::AssetId& assetId, const AZ::Data::AssetId& dependentAssetId);
virtual ProcessedObjectStoreContainer& GetProcessedObjects();
virtual const ProcessedObjectStoreContainer& GetProcessedObjects() const;
virtual ProductAssetDependencyContainer& GetRegisteredProductAssetDependencies();
virtual const ProductAssetDependencyContainer& GetRegisteredProductAssetDependencies() const;
virtual void SetPlatformTags(AZ::PlatformTagSet tags);
virtual const AZ::PlatformTagSet& GetPlatformTags() const;
virtual const AZ::Uuid& GetSourceUuid() const;
@@ -57,7 +66,8 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
NamedPrefabContainer m_prefabs;
ProcessedObjectStoreContainer m_products;
AZStd::vector<AZStd::string> m_delayedDelete;
ProductAssetDependencyContainer m_registeredProductAssetDependencies;
AZ::PlatformTagSet m_platformTags;
AZ::Uuid m_sourceUuid;
bool m_isIterating{ false };
@@ -56,6 +56,16 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
return *m_asset;
}
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& ProcessedObjectStore::GetReferencedAssets()
{
return m_referencedAssets;
}
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& ProcessedObjectStore::GetReferencedAssets() const
{
return m_referencedAssets;
}
AZStd::unique_ptr<AZ::Data::AssetData> ProcessedObjectStore::ReleaseAsset()
{
return AZStd::move(m_asset);
@@ -48,6 +48,10 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
AZ::Data::AssetData& GetAsset();
AZStd::unique_ptr<AZ::Data::AssetData> ReleaseAsset();
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& GetReferencedAssets();
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& GetReferencedAssets() const;
const AZStd::string& GetId() const;
private:
@@ -55,6 +59,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
SerializerFunction m_assetSerializer;
AZStd::unique_ptr<AZ::Data::AssetData> m_asset;
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> m_referencedAssets;
AZStd::string m_uniqueId;
};
@@ -28,16 +28,23 @@ namespace AzToolsFramework::Prefab::SpawnableUtils
AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom)
{
AzFramework::Spawnable spawnable;
[[maybe_unused]] bool result = CreateSpawnable(spawnable, prefabDom);
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> referencedAssets;
[[maybe_unused]] bool result = CreateSpawnable(spawnable, prefabDom, referencedAssets);
AZ_Assert(result,
"Failed to Load Prefab Instance from given Prefab DOM while Spawnable creation.");
return spawnable;
}
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom)
{
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> referencedAssets;
return CreateSpawnable(spawnable, prefabDom, referencedAssets);
}
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom, AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets)
{
Instance instance;
if (Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(instance, prefabDom,
if (Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(instance, prefabDom, referencedAssets,
Prefab::PrefabDomUtils::LoadInstanceFlags::AssignRandomEntityId)) // Always assign random entity ids because the spawnable is
// going to be used to create clones of the entities.
{
@@ -19,6 +19,7 @@ namespace AzToolsFramework::Prefab::SpawnableUtils
{
AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom);
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom);
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom, AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets);
void SortEntitiesByTransformHierarchy(AzFramework::Spawnable& spawnable);
} // namespace AzToolsFramework::Prefab::SpawnableUtils
@@ -0,0 +1,85 @@
/*
* 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/std/smart_ptr/make_shared.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerNullComponent.h>
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <AzToolsFramework/Thumbnails/MissingThumbnail.h>
namespace AzToolsFramework
{
namespace Thumbnailer
{
ThumbnailerNullComponent::ThumbnailerNullComponent() :
m_nullThumbnail()
{
}
ThumbnailerNullComponent::~ThumbnailerNullComponent() = default;
void ThumbnailerNullComponent::Activate()
{
BusConnect();
}
void ThumbnailerNullComponent::Deactivate()
{
BusDisconnect();
}
void ThumbnailerNullComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<ThumbnailerNullComponent, AZ::Component>();
}
}
void ThumbnailerNullComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("ThumbnailerService", 0x65422b97));
}
void ThumbnailerNullComponent::RegisterContext(const char* /*contextName*/)
{
}
void ThumbnailerNullComponent::UnregisterContext(const char* /*contextName*/)
{
}
bool ThumbnailerNullComponent::HasContext(const char* /*contextName*/) const
{
return false;
}
void ThumbnailerNullComponent::RegisterThumbnailProvider(AzToolsFramework::Thumbnailer::SharedThumbnailProvider /*provider*/, const char* /*contextName*/)
{
}
void ThumbnailerNullComponent::UnregisterThumbnailProvider(const char* /*providerName*/, const char* /*contextName*/)
{
}
AzToolsFramework::Thumbnailer::SharedThumbnail ThumbnailerNullComponent::GetThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey /*key*/, const char* /*contextName*/)
{
return m_nullThumbnail;
}
bool ThumbnailerNullComponent::IsLoading(AzToolsFramework::Thumbnailer::SharedThumbnailKey /*thumbnailKey*/, const char* /*contextName*/)
{
return false;
}
} // namespace Thumbnailer
} // namespace AzToolsFramework
@@ -0,0 +1,60 @@
/*
* 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/Component/Component.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.h>
// ThumbnailerNullComponent is an alternative to ThumbnailerComponent that can be used by tools that don't use Qt.
// It doesn't do anything (hence "null"), but it allows the system and editor components that rely on the ThumbnailService
// to start up and function.
namespace AzToolsFramework
{
namespace Thumbnailer
{
class ThumbnailContext;
class ThumbnailerNullComponent
: public AZ::Component
, public AzToolsFramework::Thumbnailer::ThumbnailerRequestsBus::Handler
{
public:
AZ_COMPONENT(ThumbnailerNullComponent, "{8009D651-3FAA-9815-B99E-AF174A3B29D4}")
ThumbnailerNullComponent();
virtual ~ThumbnailerNullComponent();
//////////////////////////////////////////////////////////////////////////
// AZ::Component
//////////////////////////////////////////////////////////////////////////
void Activate() override;
void Deactivate() override;
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
//////////////////////////////////////////////////////////////////////////
// ThumbnailerRequests
//////////////////////////////////////////////////////////////////////////
void RegisterContext(const char* contextName) override;
void UnregisterContext(const char* contextName) override;
bool HasContext(const char* contextName) const override;
void RegisterThumbnailProvider(AzToolsFramework::Thumbnailer::SharedThumbnailProvider provider, const char* contextName) override;
void UnregisterThumbnailProvider(const char* providerName, const char* contextName) override;
AzToolsFramework::Thumbnailer::SharedThumbnail GetThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey, const char* contextName) override;
bool IsLoading(AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey, const char* contextName) override;
private:
AzToolsFramework::Thumbnailer::SharedThumbnail m_nullThumbnail;
};
} // Thumbnailer
} // namespace AzToolsFramework
@@ -63,29 +63,6 @@ namespace AzToolsFramework
void EditorNonUniformScaleComponent::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 EditorNonUniformScaleComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
@@ -1287,7 +1287,6 @@ namespace AzToolsFramework
Field("Cached World Transform Parent", &TransformComponent::m_cachedWorldTransformParent)->
Field("Parent Activation Transform Mode", &TransformComponent::m_parentActivationTransformMode)->
Field("IsStatic", &TransformComponent::m_isStatic)->
Field("Sync Enabled", &TransformComponent::m_netSyncEnabled)->
Field("InterpolatePosition", &TransformComponent::m_interpolatePosition)->
Field("InterpolateRotation", &TransformComponent::m_interpolateRotation)->
Version(9, &Internal::TransformComponentDataConverter);
@@ -1322,21 +1321,7 @@ namespace AzToolsFramework
Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide)->
DataElement(AZ::Edit::UIHandlers::Default, &TransformComponent::m_cachedWorldTransform, "Cached World Transform", "")->
Attribute(AZ::Edit::Attributes::SliceFlags, AZ::Edit::SliceFlags::NotPushable)->
Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide)->
ClassElement(AZ::Edit::ClassElements::Group, "Network Sync")->
Attribute(AZ::Edit::Attributes::AutoExpand, true)->
DataElement(AZ::Edit::UIHandlers::Default, &TransformComponent::m_netSyncEnabled, "Sync to replicas", "Sync to network replicas.")->
DataElement(AZ::Edit::UIHandlers::ComboBox, &TransformComponent::m_interpolatePosition,
"Position Interpolation", "Enable local interpolation of position.")->
EnumAttribute(AZ::InterpolationMode::NoInterpolation, "None")->
EnumAttribute(AZ::InterpolationMode::LinearInterpolation, "Linear")->
DataElement(AZ::Edit::UIHandlers::ComboBox, &TransformComponent::m_interpolateRotation,
"Rotation Interpolation", "Enable local interpolation of rotation.")->
EnumAttribute(AZ::InterpolationMode::NoInterpolation, "None")->
EnumAttribute(AZ::InterpolationMode::LinearInterpolation, "Linear");
Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide);
ptrEdit->Class<EditorTransform>("Values", "XYZ PYR")->
DataElement(AZ::Edit::UIHandlers::Default, &EditorTransform::m_translate, "Translate", "Local Position (Relative to parent) in meters.")->
@@ -63,23 +63,40 @@ namespace AzToolsFramework
void PropertyManagerComponent::Deactivate()
{
// Delete all remaining auto-delete or built-in handlers.
for (auto it = m_builtInHandlers.begin(); it != m_builtInHandlers.end(); ++it)
{
UnregisterPropertyType(*it);
#ifdef AZ_DEBUG_BUILD
// For debug builds, we'll take the extra time to delete each handler that we're deleting from m_Handlers.
// We loop through m_Handlers below to ensure that we don't have any other handlers still registered after
// we've deleted these.
AZStd::erase_if(m_Handlers, [it](const auto& item) {
auto const& [key, value] = item;
return (key == (*it)->GetHandlerName()) && (value == (*it));
});
#endif
delete *it;
}
m_builtInHandlers.clear();
#ifdef _DEBUG
#ifdef AZ_DEBUG_BUILD
// Loop through all the remaining registered handlers (if any) and print out an error, as these all are probably memory
// leaks. UnregisterPropertyType should have been called on these already, and their pointers should have been deleted
// by the caller.
auto it = m_Handlers.begin();
while (it != m_Handlers.end())
{
AZ_Error("PropertyManager", false, "Property Handler 0x%08x is still registered during shutdown", it->first);
++it;
}
#endif
#endif
m_Handlers.clear();
m_DefaultHandlers.clear();
PropertyTypeRegistrationMessages::Bus::Handler::BusDisconnect();
}
@@ -142,6 +159,16 @@ namespace AzToolsFramework
}
++defaultIt;
}
if (pHandler->AutoDelete())
{
m_builtInHandlers.erase(AZStd::remove(m_builtInHandlers.begin(), m_builtInHandlers.end(), pHandler),
m_builtInHandlers.end());
AZ_Assert(false,
"Handlers with AutoDelete set should not call UnregisterPropertyType. To fix, do one of the following:\n"
" 1. Set AutoDelete to false in the handler, call UnregisterPropertyType, and the caller should delete the handler.\n"
" 2. Set AutoDelete to true in the handler and do NOT call UnregisterPropertyType or delete the handler.");
}
}
@@ -13,6 +13,9 @@
#include "EditorTransformComponentSelection.h"
#include <AzCore/std/algorithm.h>
#include <AzCore/Math/Matrix3x3.h>
#include <AzCore/Math/Matrix3x4.h>
#include <AzCore/Math/Matrix4x4.h>
#include <AzCore/Math/VectorConversions.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Viewport/CameraState.h>
@@ -234,16 +237,15 @@ namespace AzToolsFramework
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt();
}
static bool IndividualSelect(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
static bool IndividualSelect(const AzFramework::ClickDetector::ClickOutcome clickOutcome)
{
return mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down;
return clickOutcome == AzFramework::ClickDetector::ClickOutcome::Click;
}
static bool AdditiveIndividualSelect(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
static bool AdditiveIndividualSelect(
const AzFramework::ClickDetector::ClickOutcome clickOutcome, const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
return mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down &&
return clickOutcome == AzFramework::ClickDetector::ClickOutcome::Click &&
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl() &&
!mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt();
}
@@ -1780,6 +1782,25 @@ namespace AzToolsFramework
m_cachedEntityIdUnderCursor = m_editorHelpers->HandleMouseInteraction(cameraState, mouseInteraction);
const AzFramework::ClickDetector::ClickEvent selectClickEvent = [&mouseInteraction] {
if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
{
if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down)
{
return AzFramework::ClickDetector::ClickEvent::Down;
}
if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up)
{
return AzFramework::ClickDetector::ClickEvent::Up;
}
}
return AzFramework::ClickDetector::ClickEvent::Nil;
}();
m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
const auto clickOutcome = m_clickDetector.DetectClick(selectClickEvent, m_cursorState.CursorDelta());
// for entities selected with no bounds of their own (just TransformComponent)
// check selection against the selection indicator aabb
for (AZ::EntityId entityId : m_selectedEntityIds)
@@ -1838,7 +1859,7 @@ namespace AzToolsFramework
if (!m_selectedEntityIds.empty())
{
// select/deselect (add/remove) entities with ctrl held
if (Input::AdditiveIndividualSelect(mouseInteraction))
if (Input::AdditiveIndividualSelect(clickOutcome, mouseInteraction))
{
if (SelectDeselect(entityIdUnderCursor))
{
@@ -2020,7 +2041,7 @@ namespace AzToolsFramework
}
// standard toggle selection
if (Input::IndividualSelect(mouseInteraction))
if (Input::IndividualSelect(clickOutcome))
{
SelectDeselect(entityIdUnderCursor);
}
@@ -2523,7 +2544,7 @@ namespace AzToolsFramework
// create the cluster for changing transform mode
ViewportUi::ViewportUiRequestBus::EventResult(
m_transformModeClusterId, ViewportUi::DefaultViewportId,
&ViewportUi::ViewportUiRequestBus::Events::CreateCluster);
&ViewportUi::ViewportUiRequestBus::Events::CreateCluster, ViewportUi::Alignment::TopLeft);
// create and register the buttons (strings correspond to icons even if the values appear different)
m_translateButtonId = RegisterClusterButton(m_transformModeClusterId, "Move");
@@ -3264,6 +3285,8 @@ namespace AzToolsFramework
const auto modifiers = ViewportInteraction::KeyboardModifiers(
ViewportInteraction::TranslateKeyboardModifiers(QApplication::queryKeyboardModifiers()));
m_cursorState.Update();
HandleAccents(
!m_selectedEntityIds.empty(), m_cachedEntityIdUnderCursor,
modifiers.Ctrl(), m_hoveredEntityId,
@@ -17,6 +17,8 @@
#include <AzCore/std/optional.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Components/CameraBus.h>
#include <AzFramework/Viewport/ClickDetector.h>
#include <AzFramework/Viewport/CursorState.h>
#include <AzToolsFramework/API/EditorCameraBus.h>
#include <AzToolsFramework/Commands/EntityManipulatorCommand.h>
#include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
@@ -35,37 +37,37 @@ namespace AzToolsFramework
{
class EditorVisibleEntityDataCache;
using EntityIdSet = AZStd::unordered_set<AZ::EntityId>; ///< Alias for unordered_set of EntityIds.
using EntityIdSet = AZStd::unordered_set<AZ::EntityId>; //!< Alias for unordered_set of EntityIds.
/// Entity related data required by manipulators during action.
//! Entity related data required by manipulators during action.
struct EntityIdManipulatorLookup
{
AZ::Transform m_initial; /// Transform of Entity at mouse down on manipulator.
AZ::Transform m_initial; //!< Transform of Entity at mouse down on manipulator.
};
/// Alias for a mapping between EntityIds and Entity related data required by manipulators.
//! Alias for a mapping between EntityIds and Entity related data required by manipulators.
using EntityIdManipulatorLookups = AZStd::unordered_map<AZ::EntityId, EntityIdManipulatorLookup>;
/// Generic wrapper to handle specific manipulators controlling 1-* entities.
//! Generic wrapper to handle specific manipulators controlling 1-* entities.
struct EntityIdManipulators
{
EntityIdManipulatorLookups m_lookups; ///< Mapping between the EntityId and the transform of the Entity at
///< the point a manipulator started adjusting it.
AZStd::unique_ptr<Manipulators> m_manipulators; ///< The aggregate manipulator currently in use.
EntityIdManipulatorLookups m_lookups; //!< Mapping between the EntityId and the transform of the Entity at
//!< the point a manipulator started adjusting it.
AZStd::unique_ptr<Manipulators> m_manipulators; //!< The aggregate manipulator currently in use.
};
/// Store translation and orientation only (no scale).
//! Store translation and orientation only (no scale).
struct Frame
{
AZ::Vector3 m_translation = AZ::Vector3::CreateZero(); ///< Position of frame.
AZ::Quaternion m_orientation = AZ::Quaternion::CreateIdentity(); ///< Orientation of frame.
AZ::Vector3 m_translation = AZ::Vector3::CreateZero(); //!< Position of frame.
AZ::Quaternion m_orientation = AZ::Quaternion::CreateIdentity(); //!< Orientation of frame.
};
/// Temporary manipulator frame used during selection.
//! Temporary manipulator frame used during selection.
struct OptionalFrame
{
/// What part of the transform did we pick (when using ditto on
/// the manipulator). This will depend on the transform mode we're in.
//! What part of the transform did we pick (when using ditto on
//! the manipulator). This will depend on the transform mode we're in.
struct PickType
{
enum : AZ::u8
@@ -83,29 +85,29 @@ namespace AzToolsFramework
bool PickedTranslation() const;
bool PickedOrientation() const;
/// Clear all state associated with the frame.
//! Clear all state associated with the frame.
void Reset();
/// Clear only picked translation state.
//! Clear only picked translation state.
void ResetPickedTranslation();
/// Clear only picked orientation state.
//! Clear only picked orientation state.
void ResetPickedOrientation();
AZ::EntityId m_pickedEntityIdOverride; ///< 'Picked' Entity - frame and parent space relative to this if active.
AZStd::optional<AZ::Vector3> m_translationOverride; ///< Translation override, if set, reset when selection is empty.
AZStd::optional<AZ::Quaternion> m_orientationOverride; ///< Orientation override, if set, reset when selection is empty.
AZ::u8 m_pickTypes = PickType::None; ///< What mode(s) were we in when picking an EntityId override.
AZ::EntityId m_pickedEntityIdOverride; //!< 'Picked' Entity - frame and parent space relative to this if active.
AZStd::optional<AZ::Vector3> m_translationOverride; //!< Translation override, if set, reset when selection is empty.
AZStd::optional<AZ::Quaternion> m_orientationOverride; //!< Orientation override, if set, reset when selection is empty.
AZ::u8 m_pickTypes = PickType::None; //!< What mode(s) were we in when picking an EntityId override.
};
/// What frame/space is the manipulator currently operating in.
//! What frame/space is the manipulator currently operating in.
enum class ReferenceFrame
{
Local, /// The local space of the individual entity.
Parent, /// The parent space of the individual entity (world space if no parent exists).
World, /// World space (space aligned to world axes - identity).
Local, //!< The local space of the individual entity.
Parent, //!< The parent space of the individual entity (world space if no parent exists).
World, //!< World space (space aligned to world axes - identity).
};
/// Entity selection/interaction handling.
/// Provide a suite of functionality for manipulating entities, primarily through their TransformComponent.
//! Entity selection/interaction handling.
//! Provide a suite of functionality for manipulating entities, primarily through their TransformComponent.
class EditorTransformComponentSelection
: public ViewportInteraction::ViewportSelectionRequests
, private EditorEventsBus::Handler
@@ -127,15 +129,15 @@ namespace AzToolsFramework
EditorTransformComponentSelection& operator=(const EditorTransformComponentSelection&) = delete;
virtual ~EditorTransformComponentSelection();
/// Register entity manipulators with the ManipulatorManager.
/// After being registered, the entity manipulators will draw and check for input.
//! Register entity manipulators with the ManipulatorManager.
//! After being registered, the entity manipulators will draw and check for input.
void RegisterManipulator();
/// Unregister entity manipulators with the ManipulatorManager.
/// No longer draw or respond to input.
//! Unregister entity manipulators with the ManipulatorManager.
//! No longer draw or respond to input.
void UnregisterManipulator();
/// ViewportInteraction::ViewportSelectionRequests
/// Intercept all viewport mouse events and respond to inputs.
//! ViewportInteraction::ViewportSelectionRequests
//! Intercept all viewport mouse events and respond to inputs.
bool HandleMouseInteraction(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction) override;
void DisplayViewportSelection(
@@ -145,9 +147,9 @@ namespace AzToolsFramework
const AzFramework::ViewportInfo& viewportInfo,
AzFramework::DebugDisplayRequests& debugDisplay) override;
/// Add an entity to the current selection
//! Add an entity to the current selection
void AddEntityToSelection(AZ::EntityId entityId);
/// Remove an entity from the current selection
//! Remove an entity from the current selection
void RemoveEntityFromSelection(AZ::EntityId entityId);
private:
@@ -161,8 +163,8 @@ namespace AzToolsFramework
void ClearManipulatorTranslationOverride();
void ClearManipulatorOrientationOverride();
/// Handle an event triggered by the user to clear any manipulator overrides.
/// Delegate to either translation or orientation reset/clear depending on the state we're in.
//! Handle an event triggered by the user to clear any manipulator overrides.
//! Delegate to either translation or orientation reset/clear depending on the state we're in.
void DelegateClearManipulatorOverride();
void ToggleCenterPivotSelection();
@@ -251,63 +253,65 @@ namespace AzToolsFramework
void SetEntityLocalScale(AZ::EntityId entityId, const AZ::Vector3& localScale);
void SetEntityLocalRotation(AZ::EntityId entityId, const AZ::Vector3& localRotation);
AZ::EntityId m_hoveredEntityId; ///< What EntityId is the mouse currently hovering over (if any).
AZ::EntityId m_cachedEntityIdUnderCursor; ///< Store the EntityId on each mouse move for use in Display.
AZ::EntityId m_editorCameraComponentEntityId; ///< The EditorCameraComponent EntityId if it is set.
EntityIdSet m_selectedEntityIds; ///< Represents the current entities in the selection.
AZ::EntityId m_hoveredEntityId; //!< What EntityId is the mouse currently hovering over (if any).
AZ::EntityId m_cachedEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display.
AZ::EntityId m_editorCameraComponentEntityId; //!< The EditorCameraComponent EntityId if it is set.
EntityIdSet m_selectedEntityIds; //!< Represents the current entities in the selection.
const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; ///< A cache of packed EntityData that can be
///< iterated over efficiently without the need
///< to make individual EBus calls.
AZStd::unique_ptr<EditorHelpers> m_editorHelpers; ///< Editor visualization of entities (icons, shapes, debug visuals etc).
EntityIdManipulators m_entityIdManipulators; ///< Mapping from a Manipulator to potentially many EntityIds.
const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< A cache of packed EntityData that can be
//!< iterated over efficiently without the need
//!< to make individual EBus calls.
AZStd::unique_ptr<EditorHelpers> m_editorHelpers; //!< Editor visualization of entities (icons, shapes, debug visuals etc).
EntityIdManipulators m_entityIdManipulators; //!< Mapping from a Manipulator to potentially many EntityIds.
EditorBoxSelect m_boxSelect; ///< Type responsible for handling box select.
AZStd::unique_ptr<EntityManipulatorCommand> m_manipulatorMoveCommand; ///< Track adjustments to manipulator translation and orientation (during mouse press/move).
AZStd::vector<AZStd::unique_ptr<QAction>> m_actions; ///< What actions are tied to this handler.
ViewportInteraction::KeyboardModifiers m_previousModifiers; ///< What modifiers were held last frame.
EditorContextMenu m_contextMenu; ///< Viewport right click context menu.
OptionalFrame m_pivotOverrideFrame; ///< Has a pivot override been set.
Mode m_mode = Mode::Translation; ///< Manipulator mode - default to translation.
Pivot m_pivotMode = Pivot::Object; ///< Entity pivot mode - default to object (authored root).
ReferenceFrame m_referenceFrame = ReferenceFrame::Parent; ///< What reference frame is the Manipulator currently operating in.
Frame m_axisPreview; ///< Axes of entity at the time of mouse down to indicate delta of translation.
bool m_triedToRefresh = false; ///< Did a refresh event occur to recalculate the current Manipulator transform.
bool m_didSetSelectedEntities = false; ///< Was EditorTransformComponentSelection responsible for the most recent entity selection change.
bool m_selectedEntityIdsAndManipulatorsDirty = false; ///< Do the active manipulators need to recalculated after a modification (lock/visibility etc).
bool m_transformChangedInternally = false; ///< Was an OnTransformChanged event triggered internally or not.
ViewportUi::ClusterId m_transformModeClusterId; ///< Id of the Viewport UI cluster for changing transform mode.
ViewportUi::ButtonId m_translateButtonId; ///< Id of the Viewport UI button for translate mode.
ViewportUi::ButtonId m_rotateButtonId; ///< Id of the Viewport UI button for rotate mode.
ViewportUi::ButtonId m_scaleButtonId; ///< Id of the Viewport UI button for scale mode.
AZ::Event<ViewportUi::ButtonId>::Handler m_transformModeSelectionHandler; ///< Event handler for the Viewport UI cluster.
EditorBoxSelect m_boxSelect; //!< Type responsible for handling box select.
AZStd::unique_ptr<EntityManipulatorCommand> m_manipulatorMoveCommand; //!< Track adjustments to manipulator translation and orientation (during mouse press/move).
AZStd::vector<AZStd::unique_ptr<QAction>> m_actions; //!< What actions are tied to this handler.
ViewportInteraction::KeyboardModifiers m_previousModifiers; //!< What modifiers were held last frame.
EditorContextMenu m_contextMenu; //!< Viewport right click context menu.
OptionalFrame m_pivotOverrideFrame; //!< Has a pivot override been set.
Mode m_mode = Mode::Translation; //!< Manipulator mode - default to translation.
Pivot m_pivotMode = Pivot::Object; //!< Entity pivot mode - default to object (authored root).
ReferenceFrame m_referenceFrame = ReferenceFrame::Parent; //!< What reference frame is the Manipulator currently operating in.
Frame m_axisPreview; //!< Axes of entity at the time of mouse down to indicate delta of translation.
bool m_triedToRefresh = false; //!< Did a refresh event occur to recalculate the current Manipulator transform.
bool m_didSetSelectedEntities = false; //!< Was EditorTransformComponentSelection responsible for the most recent entity selection change.
bool m_selectedEntityIdsAndManipulatorsDirty = false; //!< Do the active manipulators need to recalculated after a modification (lock/visibility etc).
bool m_transformChangedInternally = false; //!< Was an OnTransformChanged event triggered internally or not.
ViewportUi::ClusterId m_transformModeClusterId; //!< Id of the Viewport UI cluster for changing transform mode.
ViewportUi::ButtonId m_translateButtonId; //!< Id of the Viewport UI button for translate mode.
ViewportUi::ButtonId m_rotateButtonId; //!< Id of the Viewport UI button for rotate mode.
ViewportUi::ButtonId m_scaleButtonId; //!< Id of the Viewport UI button for scale mode.
AZ::Event<ViewportUi::ButtonId>::Handler m_transformModeSelectionHandler; //!< Event handler for the Viewport UI cluster.
AzFramework::ClickDetector m_clickDetector; //!< Detect different types of mouse click.
AzFramework::CursorState m_cursorState; //!< Track the mouse position and delta movement each frame.
};
/// The ETCS (EntityTransformComponentSelection) namespace contains functions and data used exclusively by
/// the EditorTransformComponentSelection type. Functions in this namespace are exposed to facilitate testing
/// and should not be used outside of EditorTransformComponentSelection or EditorTransformComponentSelectionTests.
//! The ETCS (EntityTransformComponentSelection) namespace contains functions and data used exclusively by
//! the EditorTransformComponentSelection type. Functions in this namespace are exposed to facilitate testing
//! and should not be used outside of EditorTransformComponentSelection or EditorTransformComponentSelectionTests.
namespace ETCS
{
/// The result from calculating the entity (transform component) orientation.
/// Does the entity have a parent or not, and what orientation should the manipulator have when
/// displayed at the object pivot (determined by the entity hierarchy and what modifiers are held).
//! The result from calculating the entity (transform component) orientation.
//! Does the entity have a parent or not, and what orientation should the manipulator have when
//! displayed at the object pivot (determined by the entity hierarchy and what modifiers are held).
struct PivotOrientationResult
{
AZ::Quaternion m_worldOrientation;
AZ::EntityId m_parentId;
};
/// Calculate the orientation for an individual entity based on the incoming reference frame.
/// Note: If the entity is in a hierarchy the Parent reference frame will return the orientation of the parent.
//! Calculate the orientation for an individual entity based on the incoming reference frame.
//! Note: If the entity is in a hierarchy the Parent reference frame will return the orientation of the parent.
PivotOrientationResult CalculatePivotOrientation(AZ::EntityId entityId, ReferenceFrame referenceFrame);
/// Calculate the orientation for a group of entities based on the incoming reference frame.
//! Calculate the orientation for a group of entities based on the incoming reference frame.
template<typename EntityIdMap>
PivotOrientationResult CalculatePivotOrientationForEntityIds(
const EntityIdMap& entityIdMap, const ReferenceFrame referenceFrame);
/// Calculate the orientation for a group of entities based on the incoming
/// reference frame with possible pivot override.
//! Calculate the orientation for a group of entities based on the incoming
//! reference frame with possible pivot override.
template<typename EntityIdMap>
PivotOrientationResult CalculateSelectionPivotOrientation(
const EntityIdMap& entityIdMap, const OptionalFrame& pivotOverrideFrame,
@@ -41,6 +41,28 @@ namespace AzToolsFramework::ViewportUi::Internal
}
}
static Qt::Alignment GetQtAlignment(Alignment align)
{
switch (align)
{
case Alignment::TopRight:
return Qt::AlignTop | Qt::AlignRight;
case Alignment::TopLeft:
return Qt::AlignTop | Qt::AlignLeft;
case Alignment::BottomRight:
return Qt::AlignBottom | Qt::AlignRight;
case Alignment::BottomLeft:
return Qt::AlignBottom | Qt::AlignLeft;
case Alignment::Top:
return Qt::AlignTop;
case Alignment::Bottom:
return Qt::AlignBottom;
}
AZ_Assert(false, "ViewportUI", "Unhandled ViewportUI Alignment %d", static_cast<int>(align));
return Qt::AlignTop;
}
ViewportUiDisplay::ViewportUiDisplay(QWidget* parent, QWidget* renderOverlay)
: m_renderOverlay(renderOverlay)
, m_uiMainWindow(parent)
@@ -56,7 +78,7 @@ namespace AzToolsFramework::ViewportUi::Internal
UnparentWidgets(m_viewportUiElements);
}
void ViewportUiDisplay::AddCluster(AZStd::shared_ptr<ButtonGroup> buttonGroup)
void ViewportUiDisplay::AddCluster(AZStd::shared_ptr<ButtonGroup> buttonGroup, const Alignment align)
{
if (!buttonGroup.get())
{
@@ -66,7 +88,7 @@ namespace AzToolsFramework::ViewportUi::Internal
auto viewportUiCluster = AZStd::make_shared<ViewportUiCluster>(buttonGroup);
auto id = AddViewportUiElement(viewportUiCluster);
buttonGroup->SetViewportUiElementId(id);
PositionViewportUiElementAnchored(id, Qt::AlignTop | Qt::AlignLeft);
PositionViewportUiElementAnchored(id, GetQtAlignment(align));
}
void ViewportUiDisplay::AddClusterButton(
@@ -94,7 +116,7 @@ namespace AzToolsFramework::ViewportUi::Internal
}
}
void ViewportUiDisplay::AddSwitcher(AZStd::shared_ptr<ButtonGroup> buttonGroup)
void ViewportUiDisplay::AddSwitcher(AZStd::shared_ptr<ButtonGroup> buttonGroup, const Alignment align)
{
if (!buttonGroup.get())
{
@@ -104,7 +126,7 @@ namespace AzToolsFramework::ViewportUi::Internal
auto viewportUiSwitcher = AZStd::make_shared<ViewportUiSwitcher>(buttonGroup);
auto id = AddViewportUiElement(viewportUiSwitcher);
buttonGroup->SetViewportUiElementId(id);
PositionViewportUiElementAnchored(id, Qt::AlignTop | Qt::AlignLeft);
PositionViewportUiElementAnchored(id, GetQtAlignment(align));
}
void ViewportUiDisplay::AddSwitcherButton(const ViewportUiElementId clusterId, Button* button)
@@ -56,12 +56,12 @@ namespace AzToolsFramework::ViewportUi::Internal
ViewportUiDisplay(QWidget* parent, QWidget* renderOverlay);
~ViewportUiDisplay();
void AddCluster(AZStd::shared_ptr<ButtonGroup> buttonGroup);
void AddCluster(AZStd::shared_ptr<ButtonGroup> buttonGroup, Alignment align);
void AddClusterButton(ViewportUiElementId clusterId, Button* button);
void RemoveClusterButton(ViewportUiElementId clusterId, ButtonId buttonId);
void UpdateCluster(const ViewportUiElementId clusterId);
void AddSwitcher(AZStd::shared_ptr<ButtonGroup> buttonGroup);
void AddSwitcher(AZStd::shared_ptr<ButtonGroup> buttonGroup, Alignment align);
void AddSwitcherButton(ViewportUiElementId switcherId, Button* button);
void RemoveSwitcherButton(ViewportUiElementId switcherId, ButtonId buttonId);
void UpdateSwitcher(ViewportUiElementId switcherId);
@@ -30,18 +30,18 @@ namespace AzToolsFramework::ViewportUi
ViewportUiRequestBus::Handler::BusDisconnect();
}
const ClusterId ViewportUiManager::CreateCluster()
const ClusterId ViewportUiManager::CreateCluster(const Alignment align)
{
auto buttonGroup = AZStd::make_shared<Internal::ButtonGroup>();
m_viewportUi->AddCluster(buttonGroup);
m_viewportUi->AddCluster(buttonGroup, align);
return RegisterNewCluster(buttonGroup);
}
const SwitcherId ViewportUiManager::CreateSwitcher()
const SwitcherId ViewportUiManager::CreateSwitcher(const Alignment align)
{
auto buttonGroup = AZStd::make_shared<Internal::ButtonGroup>();
m_viewportUi->AddSwitcher(buttonGroup);
m_viewportUi->AddSwitcher(buttonGroup, align);
return RegisterNewSwitcher(buttonGroup);
}
@@ -31,8 +31,8 @@ namespace AzToolsFramework::ViewportUi
~ViewportUiManager() = default;
// ViewportUiRequestBus ...
const ClusterId CreateCluster() override;
const SwitcherId CreateSwitcher() override;
const ClusterId CreateCluster(Alignment align) override;
const SwitcherId CreateSwitcher(Alignment align) override;
void SetClusterActiveButton(ClusterId clusterId, ButtonId buttonId) override;
void SetSwitcherActiveButton(SwitcherId switcherId, ButtonId buttonId) override;
const ButtonId CreateClusterButton(ClusterId clusterId, const AZStd::string& icon) override;
@@ -41,15 +41,26 @@ namespace AzToolsFramework::ViewportUi
String
};
//! Used to anchor widgets to a specific side of the viewport.
enum class Alignment
{
TopRight,
TopLeft,
BottomRight,
BottomLeft,
Top,
Bottom
};
//! Viewport requests to interact with the Viewport UI. Viewport UI refers to the entire UI overlay (one per viewport).
//! Each widget on the Viewport UI is referred to as an element.
class ViewportUiRequests
{
public:
//! Creates and registers a cluster with the Viewport UI system.
virtual const ClusterId CreateCluster() = 0;
virtual const ClusterId CreateCluster(Alignment align) = 0;
//! Creates and registers a switcher with the Viewport UI system.
virtual const SwitcherId CreateSwitcher() = 0;
virtual const SwitcherId CreateSwitcher(Alignment align) = 0;
//! Sets the active button of the cluster. This is the button which will display as highlighted.
virtual void SetClusterActiveButton(ClusterId clusterId, ButtonId buttonId) = 0;
//! Sets the active button of the switcher. This is the button which has a text label.
@@ -72,6 +72,8 @@ set(FILES
AssetCatalog/PlatformAddressedAssetCatalogManager.cpp
Thumbnails/ThumbnailerComponent.cpp
Thumbnails/ThumbnailerComponent.h
Thumbnails/ThumbnailerNullComponent.cpp
Thumbnails/ThumbnailerNullComponent.h
Thumbnails/LoadingThumbnail.cpp
Thumbnails/LoadingThumbnail.h
Thumbnails/MissingThumbnail.cpp
@@ -120,7 +120,7 @@ namespace UnitTest
//create an undo node to apply the patch and prep for undo
PrefabUndoInstanceLink undoInstanceLinkNode("Undo Link Patch");
undoInstanceLinkNode.Capture(rootTemplateId, nestedTemplateId, aliases[0], patch, InvalidLinkId);
undoInstanceLinkNode.Capture(rootTemplateId, nestedTemplateId, aliases[0], AZStd::move(patch), InvalidLinkId);
undoInstanceLinkNode.Redo();
m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
@@ -196,7 +196,7 @@ namespace UnitTest
//create an undo node to apply the patch and prep for undo
PrefabUndoInstanceLink undoInstanceLinkNode("Undo Link Patch");
undoInstanceLinkNode.Capture(rootTemplateId, nestedTemplateId, aliases[0], linkPatch, InvalidLinkId);
undoInstanceLinkNode.Capture(rootTemplateId, nestedTemplateId, aliases[0], AZStd::move(linkPatch), InvalidLinkId);
undoInstanceLinkNode.Redo();
m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
@@ -72,7 +72,7 @@ namespace UnitTest
TEST_F(ViewportUiDisplayTestFixture, RemoveViewportUiElementRemovesElementFromViewportUi)
{
ViewportUiDisplay viewportUi(m_parentWidget, m_mockRenderOverlay);
viewportUi.AddCluster(m_buttonGroup);
viewportUi.AddCluster(m_buttonGroup, AzToolsFramework::ViewportUi::Alignment::TopLeft);
auto widget = viewportUi.GetViewportUiElement(m_buttonGroup->GetViewportUiElementId());
EXPECT_TRUE(widget.get() != nullptr);
@@ -89,7 +89,7 @@ namespace UnitTest
ViewportUiDisplay viewportUi(m_parentWidget, m_mockRenderOverlay);
viewportUi.InitializeUiOverlay();
viewportUi.AddCluster(m_buttonGroup);
viewportUi.AddCluster(m_buttonGroup, AzToolsFramework::ViewportUi::Alignment::TopLeft);
viewportUi.Update();
viewportUi.ShowViewportUiElement(m_buttonGroup->GetViewportUiElementId());
@@ -102,7 +102,7 @@ namespace UnitTest
ViewportUiDisplay viewportUi(m_parentWidget, m_mockRenderOverlay);
viewportUi.InitializeUiOverlay();
viewportUi.AddCluster(m_buttonGroup);
viewportUi.AddCluster(m_buttonGroup, AzToolsFramework::ViewportUi::Alignment::TopLeft);
viewportUi.HideViewportUiElement(m_buttonGroup->GetViewportUiElementId());
EXPECT_FALSE(viewportUi.IsViewportUiElementVisible(m_buttonGroup->GetViewportUiElementId()));
@@ -112,7 +112,7 @@ namespace UnitTest
{
ViewportUiDisplay viewportUi(m_parentWidget, m_mockRenderOverlay);
viewportUi.InitializeUiOverlay();
viewportUi.AddCluster(m_buttonGroup);
viewportUi.AddCluster(m_buttonGroup, AzToolsFramework::ViewportUi::Alignment::TopLeft);
viewportUi.Update();
auto widget = viewportUi.GetViewportUiElement(m_buttonGroup->GetViewportUiElementId());
@@ -129,7 +129,7 @@ namespace UnitTest
auto buttonGroup = AZStd::make_shared<ButtonGroup>();
buttonGroup->AddButton("");
viewportUi.AddCluster(buttonGroup);
viewportUi.AddCluster(buttonGroup, AzToolsFramework::ViewportUi::Alignment::TopLeft);
viewportUi.Update();
EXPECT_TRUE(viewportUi.GetUiMainWindow()->isVisible());
@@ -101,7 +101,7 @@ namespace UnitTest
TEST_F(ViewportUiManagerTestFixture, CreateClusterAddsNewClusterAndReturnsId)
{
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster();
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster(AzToolsFramework::ViewportUi::Alignment::TopLeft);
auto clusterEntry = m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().find(clusterId);
EXPECT_TRUE(clusterEntry != m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().end());
@@ -110,7 +110,7 @@ namespace UnitTest
TEST_F(ViewportUiManagerTestFixture, CreateClusterButtonAddsNewButtonAndReturnsId)
{
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster();
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster(AzToolsFramework::ViewportUi::Alignment::TopLeft);
auto buttonId = m_viewportManagerWrapper.GetViewportManager()->CreateClusterButton(clusterId, "");
auto clusterEntry = m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().find(clusterId);
@@ -120,7 +120,7 @@ namespace UnitTest
TEST_F(ViewportUiManagerTestFixture, SetClusterActiveButtonSetsButtonStateToActive)
{
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster();
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster(AzToolsFramework::ViewportUi::Alignment::TopLeft);
auto buttonId = m_viewportManagerWrapper.GetViewportManager()->CreateClusterButton(clusterId, "");
auto clusterEntry = m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().find(clusterId);
@@ -133,7 +133,7 @@ namespace UnitTest
TEST_F(ViewportUiManagerTestFixture, RegisterClusterEventHandlerConnectsHandlerToClusterEvent)
{
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster();
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster(AzToolsFramework::ViewportUi::Alignment::TopLeft);
auto buttonId = m_viewportManagerWrapper.GetViewportManager()->CreateClusterButton(clusterId, "");
// create a handler which will be triggered by the cluster
@@ -159,7 +159,7 @@ namespace UnitTest
TEST_F(ViewportUiManagerTestFixture, RemoveClusterRemovesClusterFromViewportUi)
{
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster();
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster(AzToolsFramework::ViewportUi::Alignment::TopLeft);
m_viewportManagerWrapper.GetViewportManager()->RemoveCluster(clusterId);
auto clusterEntry = m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().find(clusterId);
@@ -171,7 +171,7 @@ namespace UnitTest
{
m_viewportManagerWrapper.GetMockRenderOverlay()->setVisible(true);
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster();
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster(AzToolsFramework::ViewportUi::Alignment::TopLeft);
auto buttonId = m_viewportManagerWrapper.GetViewportManager()->CreateClusterButton(clusterId, "");
m_viewportManagerWrapper.GetViewportManager()->Update();
@@ -1486,7 +1486,7 @@ namespace GridMate
// Only support a single cipher suite in OpenSSL that supports:
//
// ECDHE Master key exchange using ephemeral elliptic curve diffie-hellman.
// ECDHE Key exchange using ephemeral elliptic curve diffie-hellman.
// RSA Authentication (public and private key) used to sign ECDHE parameters and can be checked against a CA.
// AES256 AES cipher for symmetric key encryption using a 256-bit key.
// GCM Mode of operation for symmetric key encryption.
@@ -97,7 +97,7 @@ namespace GridMate
// Only support a single cipher suite in OpenSSL that supports:
//
// ECDHE Master key exchange using ephemeral elliptic curve diffie-hellman.
// ECDHE Key exchange using ephemeral elliptic curve diffie-hellman.
// RSA Authentication (public and private key) used to sign ECDHE parameters and can be checked against a CA.
// AES256 AES cipher for symmetric key encryption using a 256-bit key.
// GCM Mode of operation for symmetric key encryption.
@@ -52,6 +52,6 @@ namespace GridMate
bool DataSetBase::CanSet() const
{
return m_replicaChunk ? m_replicaChunk->IsMaster() : true;
return m_replicaChunk ? m_replicaChunk->IsPrimary() : true;
}
}
@@ -43,7 +43,7 @@ namespace GridMate
struct DataSetDefaultTraits
{
/**
* \brief Should a change in DataSet value invoke a callback on a master replica chunk?
* \brief Should a change in DataSet value invoke a callback on a primary replica chunk?
*
* By default, DataSet::BindInterface<C, &C::Callback> only invokes on client/non-authoritative replica chunks.
* This switch enables the callback on server/authoritative replica chunks.
@@ -55,7 +55,7 @@ namespace GridMate
};
/**
* \brief Turns on DataSet callbacks to be invoked on the master replica as well as client replicas.
* \brief Turns on DataSet callbacks to be invoked on the primary replica as well as client replicas.
*/
struct DataSetInvokeEverywhereTraits : DataSetDefaultTraits
{
@@ -200,7 +200,7 @@ namespace GridMate
}
/**
Modify the DataSet. Call this on the Master node to change the data,
Modify the DataSet. Call this on the Primary node to change the data,
which will be propagated to all proxies.
**/
void Set(const DataType& v)
@@ -214,7 +214,7 @@ namespace GridMate
}
/**
Modify the DataSet. Call this on the Master node to change the data,
Modify the DataSet. Call this on the Primary node to change the data,
which will be propagated to all proxies.
**/
void Set(DataType&& v)
@@ -228,7 +228,7 @@ namespace GridMate
}
/**
Modify the DataSet. Call this on the Master node to change the data,
Modify the DataSet. Call this on the Primary node to change the data,
which will be propagated to all proxies.
**/
template <class ... Args>
@@ -243,7 +243,7 @@ namespace GridMate
}
/**
Modify the DataSet directly without copying it. Call this on the Master node,
Modify the DataSet directly without copying it. Call this on the Primary node,
passing in a function object that takes the value by reference, optionally
modifies the data, and returns true if the data was changed.
**/
@@ -159,7 +159,7 @@ namespace GridMate
}
/**
Modify the DataSet. Call this on the Master node to change the data,
Modify the DataSet. Call this on the Primary node to change the data,
which will be propagated to all proxies.
**/
void Set(const FieldType& v)
@@ -201,7 +201,7 @@ namespace GridMate
private:
DataSet<FieldType, MarshalerType> m_absolutePortion;
DataSet<FieldType, DeltaMarshalerType> m_relativePortion;
FieldType m_combinedValue; // the latest value on either master or proxy
FieldType m_combinedValue; // the latest value on either primary or proxy
};
//-----------------------------------------------------------------------------

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