Merge branch 'development' of https://github.com/o3de/o3de into daimini/FocusMode/setup

This commit is contained in:
Danilo Aimini
2021-09-20 17:11:09 -07:00
16 changed files with 958 additions and 115 deletions
@@ -65,15 +65,4 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
COMPONENT
Atom
)
ly_add_pytest(
NAME AutomatedTesting::AtomRenderer_HydraTests_ShaderBuildPipeline
TEST_SUITE main
PATH ${CMAKE_CURRENT_LIST_DIR}/test_Atom_ShaderBuildPipelineSuite.py
TEST_SERIAL
TIMEOUT 600
RUNTIME_DEPENDENCIES
AssetProcessor
AutomatedTesting.Assets
Editor
)
endif()
@@ -3,78 +3,19 @@ Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import shutil
def _copy_file(src_file, src_path, target_file, target_path):
# type: (str, str, str, str) -> None
"""
Copies the [src_file] located in [src_path] to the [target_file] located at [target_path].
Leaves the [target_file] unlocked for reading and writing privileges
:param src_file: The source file to copy (file name)
:param src_path: The source file's path
:param target_file: The target file to copy into (file name)
:param target_path: The target file's path
:return: None
"""
target_file_path = os.path.join(target_path, target_file)
src_file_path = os.path.join(src_path, src_file)
if os.path.exists(target_file_path):
fs.unlock_file(target_file_path)
shutil.copyfile(src_file_path, target_file_path)
def _copy_tmp_files_in_order(src_directory, file_list, dst_directory, wait_time_in_between = 0.0):
# type: (str, list, str, float) -> None
"""
This function assumes that for each file name listed in @file_list
there's file named "@filename.txt" which the original source file
but they will be copied with just the @filename (.txt removed).
"""
for filename in file_list:
src_name = f"{filename}.txt"
_copy_file(src_name, src_directory, filename, dst_directory)
if wait_time_in_between > 0.0:
print(f"Created {filename} in {dst_directory}")
general.idle_wait(wait_time_in_between)
def _remove_file(src_file, src_path):
# type: (str, str) -> None
"""
Removes the [src_file] located in [src_path].
:param src_file: The source file to copy (file name)
:param src_path: The source file's path
:return: None
"""
src_file_path = os.path.join(src_path, src_file)
if os.path.exists(src_file_path):
fs.unlock_file(src_file_path)
os.remove(src_file_path)
def _remove_files(directory, file_list):
for filename in file_list:
_remove_file(filename, directory)
def _asset_exists(cache_relative_path):
asset_id = azasset.AssetCatalogRequestBus(azbus.Broadcast, "GetAssetIdByPath", cache_relative_path, azmath.Uuid(), False)
return asset_id.is_valid()
# List of results that we want to check, this is not 100% necessary but it's a good
# practice to make it easier to debug tests.
# Here we define a tuple of tests
class Results():
azshader_was_removed = ("azshader was removed", "Failed to remove azshader")
azshader_was_compiled = ("azshader was compiled", "Failed to compile azshader")
# fmt: off
class Tests():
azshader_was_removed = ("azshader was removed", "Failed to remove azshader")
azshader_was_compiled = ("azshader was compiled", "Failed to compile azshader")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges():
"""
This test validates [ATOM-5441] Shader Builders May Fail When Multiple New Files Are Added
This test validates: "Shader Builders May Fail When Multiple New Files Are Added"
It creates source assets to compile a particular shader.
1- The first phase generates the source assets out of order and slowly. The AP should
wakeup each time one of the source dependencies appears but will fail each time. Only when the
@@ -82,6 +23,71 @@ def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges():
2- The second phase is similar as above, except that all source assets will be created
at once and We also expect that in the end the shader is built successfully.
"""
import os
import shutil
import azlmbr.asset as azasset
import azlmbr.bus as azbus
import azlmbr.legacy.general as general
import azlmbr.math as azmath
from editor_python_test_tools.utils import TestHelper as helper
from editor_python_test_tools.utils import Tracer
import ly_test_tools.environment.file_system as fs
def _copy_file(src_file, src_path, target_file, target_path):
# type: (str, str, str, str) -> None
"""
Copies the [src_file] located in [src_path] to the [target_file] located at [target_path].
Leaves the [target_file] unlocked for reading and writing privileges
:param src_file: The source file to copy (file name)
:param src_path: The source file's path
:param target_file: The target file to copy into (file name)
:param target_path: The target file's path
:return: None
"""
target_file_path = os.path.join(target_path, target_file)
src_file_path = os.path.join(src_path, src_file)
if os.path.exists(target_file_path):
fs.unlock_file(target_file_path)
shutil.copyfile(src_file_path, target_file_path)
def _copy_tmp_files_in_order(src_directory, file_list, dst_directory, wait_time_in_between=0.0):
# type: (str, list, str, float) -> None
"""
This function assumes that for each file name listed in @file_list
there's file named "@filename.txt" which the original source file
but they will be copied with just the @filename (.txt removed).
"""
for filename in file_list:
src_name = f"{filename}.txt"
_copy_file(src_name, src_directory, filename, dst_directory)
if wait_time_in_between > 0.0:
print(f"Created {filename} in {dst_directory}")
general.idle_wait(wait_time_in_between)
def _remove_file(src_file, src_path):
# type: (str, str) -> None
"""
Removes the [src_file] located in [src_path].
:param src_file: The source file to copy (file name)
:param src_path: The source file's path
:return: None
"""
src_file_path = os.path.join(src_path, src_file)
if os.path.exists(src_file_path):
fs.unlock_file(src_file_path)
os.remove(src_file_path)
def _remove_files(directory, file_list):
for filename in file_list:
_remove_file(filename, directory)
def _asset_exists(cache_relative_path):
asset_id = azasset.AssetCatalogRequestBus(azbus.Broadcast, "GetAssetIdByPath", cache_relative_path,
azmath.Uuid(), False)
return asset_id.is_valid()
# Required for automated tests
helper.init_idle()
@@ -115,14 +121,14 @@ def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges():
azshader_name = "assets/dependencyvalidation.azshader"
helper.wait_for_condition(lambda: not _asset_exists(azshader_name), 5.0)
Report.critical_result(Results.azshader_was_removed, not _asset_exists(azshader_name))
Report.critical_result(Tests.azshader_was_removed, not _asset_exists(azshader_name))
_copy_tmp_files_in_order(src_assets_subdir, file_list, game_asset_path, 1.0)
# Give enough time to AP to compile the shader
helper.wait_for_condition(lambda: _asset_exists(azshader_name), 60.0)
Report.critical_result(Results.azshader_was_compiled, _asset_exists(azshader_name))
Report.critical_result(Tests.azshader_was_compiled, _asset_exists(azshader_name))
# The first part was about compiling the shader under normal conditions.
# Let's remove the files from the previous phase and will proceed
@@ -130,7 +136,7 @@ def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges():
# ShaderAssetBuilder will only succeed when the last file becomes visible.
_remove_files(game_asset_path, reverse_file_list)
helper.wait_for_condition(lambda: not _asset_exists(azshader_name), 5.0)
Report.critical_result(Results.azshader_was_removed, not _asset_exists(azshader_name))
Report.critical_result(Tests.azshader_was_removed, not _asset_exists(azshader_name))
# Remark, if you are running this test manually from the Editor with "pyRunFile",
# You'll notice how the AP issues notifications that it fails to compile the shader
@@ -148,7 +154,7 @@ def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges():
# Give enough time to AP to compile the shader
helper.wait_for_condition(lambda: _asset_exists(azshader_name), 60.0)
Report.critical_result(Results.azshader_was_compiled, _asset_exists(azshader_name))
Report.critical_result(Tests.azshader_was_compiled, _asset_exists(azshader_name))
# The last phase of the test puts stress on potential race conditions
# when all required files appear as soon as possible.
@@ -157,7 +163,7 @@ def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges():
# Remove left over files.
_remove_files(game_asset_path, reverse_file_list)
helper.wait_for_condition(lambda: not _asset_exists(azshader_name), 5.0)
Report.critical_result(Results.azshader_was_removed, not _asset_exists(azshader_name))
Report.critical_result(Tests.azshader_was_removed, not _asset_exists(azshader_name))
# Now let's copy all the source files to the "Assets" folder as fast as possible.
_copy_tmp_files_in_order(src_assets_subdir, reverse_file_list, game_asset_path)
@@ -165,24 +171,17 @@ def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges():
# Give enough time to AP to compile the shader
helper.wait_for_condition(lambda: _asset_exists(azshader_name), 60.0)
Report.critical_result(Results.azshader_was_compiled, _asset_exists(azshader_name))
Report.critical_result(Tests.azshader_was_compiled, _asset_exists(azshader_name))
# All good, let's cleanup leftover files before closing the test.
_remove_files(game_asset_path, reverse_file_list)
helper.wait_for_condition(lambda: not _asset_exists(azshader_name), 5.0)
# Look for errors to raise.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
# All exposed python bindings are in azlmbr
import azlmbr.legacy.general as general
import azlmbr.bus as azbus
import azlmbr.asset as azasset
import azlmbr.math as azmath
# Import report and test helper utilities
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
from editor_python_test_tools.utils import Tracer
import ly_test_tools.environment.file_system as fs
Report.start_test(ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges)
Report.start_test(ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges)
@@ -41,3 +41,6 @@ class TestAutomation(EditorTestSuite):
class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest):
from atom_renderer.atom_hydra_scripts import hydra_AtomEditorComponents_DisplayMapperAdded as test_module
class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest):
from .atom_hydra_scripts import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module
@@ -1,19 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
Main suite tests for the Shader Build Pipeline.
"""
import pytest
from ly_test_tools import LAUNCHERS
from ly_test_tools.o3de.editor_test import EditorTestSuite, EditorSingleTest
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestShaderBuildPipelineMain(EditorTestSuite):
"""Holds tests for Shader Build Pipeline validation"""
class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSingleTest):
from .atom_hydra_scripts import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module
+3
View File
@@ -25,6 +25,9 @@ ly_add_target(
AZ::AzCore
AZ::AzFramework
AZ::AzNetworking
PRIVATE
Gem::EMotionFXStaticLib
Gem::PhysX.Static
AUTOGEN_RULES
*.AutoPackets.xml,AutoPackets_Header.jinja,$path/$fileprefix.AutoPackets.h
*.AutoPackets.xml,AutoPackets_Inline.jinja,$path/$fileprefix.AutoPackets.inl
@@ -0,0 +1,82 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Source/AutoGen/NetworkCharacterComponent.AutoComponent.h>
#include <PhysX/CharacterGameplayBus.h>
#include <Multiplayer/Components/NetBindComponent.h>
namespace Physics
{
class Character;
}
namespace Multiplayer
{
//! NetworkCharacterComponent
//! Provides multiplayer support for game-play player characters.
class NetworkCharacterComponent
: public NetworkCharacterComponentBase
, private PhysX::CharacterGameplayRequestBus::Handler
{
friend class NetworkCharacterComponentController;
public:
AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkCharacterComponent, s_networkCharacterComponentConcreteUuid, Multiplayer::NetworkCharacterComponentBase)
static void Reflect(AZ::ReflectContext* context);
NetworkCharacterComponent();
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("NetworkRigidBodyService"));
}
// AZ::Component
void OnInit() override {}
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
private:
void OnTranslationChangedEvent(const AZ::Vector3& translation);
void OnSyncRewind();
// CharacterGameplayRequestBus
bool IsOnGround() const override;
float GetGravityMultiplier() const override { return {}; }
void SetGravityMultiplier([[maybe_unused]] float gravityMultiplier) override {}
AZ::Vector3 GetFallingVelocity() const override { return {}; }
void SetFallingVelocity([[maybe_unused]] const AZ::Vector3& fallingVelocity) override {}
Physics::Character* m_physicsCharacter = nullptr;
Multiplayer::EntitySyncRewindEvent::Handler m_syncRewindHandler = Multiplayer::EntitySyncRewindEvent::Handler([this]() { OnSyncRewind(); });
AZ::Event<AZ::Vector3>::Handler m_translationEventHandler;
};
//! NetworkCharacterComponentController
//! This is the network controller for NetworkCharacterComponent.
//! Class provides the ability to move characters in physical space while keeping the network in-sync.
class NetworkCharacterComponentController
: public NetworkCharacterComponentControllerBase
{
public:
NetworkCharacterComponentController(NetworkCharacterComponent& parent);
// NetworkCharacterComponentControllerBase
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
//! TryMoveWithVelocity
//! Will move this character entity kinematically through physical world while also ensuring the network stays in-sync.
//! Velocity will be applied over delta-time to determine the movement amount.
//! Returns this entity's world-space position after the move.
AZ::Vector3 TryMoveWithVelocity(const AZ::Vector3& velocity, float deltaTime);
};
}
@@ -0,0 +1,90 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Integration/ActorComponentBus.h>
#include <AzCore/Component/TransformBus.h>
namespace Physics
{
class CharacterRequests;
class CharacterHitDetectionConfiguration;
}
namespace Multiplayer
{
class NetworkHitVolumesComponent
: public NetworkHitVolumesComponentBase
, private EMotionFX::Integration::ActorComponentNotificationBus::Handler
{
public:
struct AnimatedHitVolume final
{
AnimatedHitVolume
(
AzNetworking::ConnectionId connectionId,
Physics::CharacterRequests* character,
const char* hitVolumeName,
const Physics::ColliderConfiguration* colliderConfig,
const Physics::ShapeConfiguration* shapeConfig,
const uint32_t jointIndex
);
~AnimatedHitVolume() = default;
void UpdateTransform(const AZ::Transform& transform);
void SyncToCurrentTransform();
Multiplayer::RewindableObject<AZ::Transform, Multiplayer::RewindHistorySize> m_transform;
AZStd::shared_ptr<Physics::Shape> m_physicsShape;
// Cached so we don't have to do subsequent lookups by name
const Physics::ColliderConfiguration* m_colliderConfig = nullptr;
const Physics::ShapeConfiguration* m_shapeConfig = nullptr;
AZ::Transform m_colliderOffSetTransform;
const AZ::u32 m_jointIndex = 0;
};
AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHitVolumesComponent, s_networkHitVolumesComponentConcreteUuid, Multiplayer::NetworkHitVolumesComponentBase);
static void Reflect(AZ::ReflectContext* context);
NetworkHitVolumesComponent();
void OnInit() override;
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
private:
void OnPreRender(float deltaTime);
void OnTransformUpdate(const AZ::Transform& transform);
void OnSyncRewind();
void CreateHitVolumes();
void DestroyHitVolumes();
//! ActorComponentNotificationBus::Handler
//! @{
void OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance) override;
void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override;
//! @}
Physics::CharacterRequests* m_physicsCharacter = nullptr;
EMotionFX::Integration::ActorComponentRequests* m_actorComponent = nullptr;
const Physics::CharacterColliderConfiguration* m_hitDetectionConfig = nullptr;
AZStd::vector<AnimatedHitVolume> m_animatedHitVolumes;
Multiplayer::EntitySyncRewindEvent::Handler m_syncRewindHandler;
Multiplayer::EntityPreRenderEvent::Handler m_preRenderHandler;
AZ::TransformChangedEvent::Handler m_transformChangedHandler;
};
}
@@ -0,0 +1,68 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.h>
#include <AzCore/Component/TransformBus.h>
#include <Multiplayer/Components/NetBindComponent.h>
namespace Physics
{
class RigidBodyRequests;
}
namespace Multiplayer
{
//! Bus for requests to the network rigid body component.
class NetworkRigidBodyRequests : public AZ::ComponentBus
{
};
using NetworkRigidBodyRequestBus = AZ::EBus<NetworkRigidBodyRequests>;
class NetworkRigidBodyComponent final
: public NetworkRigidBodyComponentBase
, private NetworkRigidBodyRequestBus::Handler
{
friend class NetworkRigidBodyComponentController;
public:
AZ_MULTIPLAYER_COMPONENT(
Multiplayer::NetworkRigidBodyComponent, s_networkRigidBodyComponentConcreteUuid, Multiplayer::NetworkRigidBodyComponentBase);
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
NetworkRigidBodyComponent();
void OnInit() override;
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
private:
void OnTransformUpdate(const AZ::Transform& worldTm);
void OnSyncRewind();
Multiplayer::EntitySyncRewindEvent::Handler m_syncRewindHandler;
AZ::TransformChangedEvent::Handler m_transformChangedHandler;
Physics::RigidBodyRequests* m_physicsRigidBodyComponent = nullptr;
Multiplayer::RewindableObject<AZ::Transform, Multiplayer::RewindHistorySize> m_transform;
};
class NetworkRigidBodyComponentController
: public NetworkRigidBodyComponentControllerBase
{
public:
NetworkRigidBodyComponentController(NetworkRigidBodyComponent& parent);
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void HandleSendApplyImpulse(AzNetworking::IConnection* invokingConnection, const AZ::Vector3& impulse, const AZ::Vector3& worldPoint) override;
};
} // namespace Multiplayer
@@ -14,7 +14,7 @@
{% set Namespace = dataFiles[0].attrib['Namespace'] %}
{% for Component in dataFiles %}
{% if Component.attrib['Namespace'] != Namespace %}
#error "mismatched component namespaces detected in declared multiplayer components, expected {{ Namespace }} but found {{ Component.attrib['Namespace'] }}"
#error "mismatched component namespaces detected in declared multiplayer components, expected {{ Namespace }} but {{ Component.attrib['Name'] }} is using {{ Component.attrib['Namespace'] }} namespace."
{% endif %}
{% endfor %}
namespace {{ Namespace }}
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<Component
Name="NetworkCharacterComponent"
Namespace="Multiplayer"
OverrideComponent="true"
OverrideController="true"
OverrideInclude="Multiplayer/Components/NetworkCharacterComponent.h"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComponentRelation Constraint="Required" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" />
</Component>
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<Component
Name="NetworkHitVolumesComponent"
Namespace="Multiplayer"
OverrideComponent="true"
OverrideController="false"
OverrideInclude="Multiplayer/Components/NetworkHitVolumesComponent.h"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComponentRelation Constraint="Weak" HasController="false" Name="TransformComponent" Namespace="AzFramework" Include="AzFramework/Components/TransformComponent.h" />
</Component>
@@ -0,0 +1,18 @@
<?xml version="1.0"?>
<Component
Name="NetworkRigidBodyComponent"
Namespace="Multiplayer"
OverrideComponent="true"
OverrideController="true"
OverrideInclude="Multiplayer/Components/NetworkRigidBodyComponent.h"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComponentRelation Constraint="Required" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" />
<RemoteProcedure Name="SendApplyImpulse" InvokeFrom="Server" HandleOn="Authority" IsPublic="true" IsReliable="true" GenerateEventBindings="false" Description="Applies an impulse">
<Param Type="AZ::Vector3" Name="impulse" />
<Param Type="AZ::Vector3" Name="worldPoint" />
</RemoteProcedure>
</Component>
@@ -0,0 +1,209 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Multiplayer/Components/NetworkCharacterComponent.h>
#include <Multiplayer/Components/NetworkRigidBodyComponent.h>
#include <AzFramework/Visibility/EntityBoundsUnionBus.h>
#include <AzFramework/Physics/CharacterBus.h>
#include <AzFramework/Physics/Character.h>
#include <Multiplayer/Components/NetworkTransformComponent.h>
#include <Multiplayer/NetworkTime/INetworkTime.h>
#include <PhysXCharacters/API/CharacterController.h>
#include <PhysX/PhysXLocks.h>
#include <PhysX/Utils.h>
namespace Multiplayer
{
bool CollisionLayerBasedControllerFilter(const physx::PxController& controllerA, const physx::PxController& controllerB)
{
PHYSX_SCENE_READ_LOCK(controllerA.getActor()->getScene());
physx::PxRigidDynamic* actorA = controllerA.getActor();
physx::PxRigidDynamic* actorB = controllerB.getActor();
if (actorA && actorA->getNbShapes() > 0 && actorB && actorB->getNbShapes() > 0)
{
physx::PxShape* shapeA = nullptr;
actorA->getShapes(&shapeA, 1, 0);
physx::PxFilterData filterDataA = shapeA->getSimulationFilterData();
physx::PxShape* shapeB = nullptr;
actorB->getShapes(&shapeB, 1, 0);
physx::PxFilterData filterDataB = shapeB->getSimulationFilterData();
return PhysX::Utils::Collision::ShouldCollide(filterDataA, filterDataB);
}
return true;
}
physx::PxQueryHitType::Enum CollisionLayerBasedObjectPreFilter(
const physx::PxFilterData& filterData,
const physx::PxShape* shape,
const physx::PxRigidActor* actor,
[[maybe_unused]] physx::PxHitFlags& queryFlags)
{
// non-kinematic dynamic bodies should not impede the movement of the character
if (actor->getConcreteType() == physx::PxConcreteType::eRIGID_DYNAMIC)
{
const physx::PxRigidDynamic* rigidDynamic = static_cast<const physx::PxRigidDynamic*>(actor);
bool isKinematic = (rigidDynamic->getRigidBodyFlags() & physx::PxRigidBodyFlag::eKINEMATIC);
if (isKinematic)
{
const PhysX::ActorData* actorData = PhysX::Utils::GetUserData(rigidDynamic);
if (actorData)
{
const AZ::EntityId entityId = actorData->GetEntityId();
if (Multiplayer::NetworkRigidBodyRequestBus::FindFirstHandler(entityId) != nullptr)
{
// Network rigid bodies are kinematic on the client but dynamic on the server,
// hence filtering treats these actors as dynamic to support client prediction and avoid desyncs
isKinematic = false;
}
}
}
if (!isKinematic)
{
return physx::PxQueryHitType::eNONE;
}
}
// all other cases should be determined by collision filters
if (PhysX::Utils::Collision::ShouldCollide(filterData, shape->getSimulationFilterData()))
{
return physx::PxQueryHitType::eBLOCK;
}
return physx::PxQueryHitType::eNONE;
}
void NetworkCharacterComponent::NetworkCharacterComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<NetworkCharacterComponent, NetworkCharacterComponentBase>()
->Version(1);
}
NetworkCharacterComponentBase::Reflect(context);
}
NetworkCharacterComponent::NetworkCharacterComponent()
: m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); })
{
}
void NetworkCharacterComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
Physics::CharacterRequests* characterRequests = Physics::CharacterRequestBus::FindFirstHandler(GetEntityId());
m_physicsCharacter = (characterRequests != nullptr) ? characterRequests->GetCharacter() : nullptr;
GetNetBindComponent()->AddEntitySyncRewindEventHandler(m_syncRewindHandler);
if (m_physicsCharacter)
{
auto controller = static_cast<PhysX::CharacterController*>(m_physicsCharacter);
controller->SetFilterFlags(physx::PxQueryFlag::eSTATIC | physx::PxQueryFlag::eDYNAMIC | physx::PxQueryFlag::ePREFILTER);
if (auto callbackManager = controller->GetCallbackManager())
{
callbackManager->SetControllerFilter(CollisionLayerBasedControllerFilter);
callbackManager->SetObjectPreFilter(CollisionLayerBasedObjectPreFilter);
}
}
if (!HasController())
{
GetNetworkTransformComponent()->TranslationAddEvent(m_translationEventHandler);
}
}
void NetworkCharacterComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
void NetworkCharacterComponent::OnTranslationChangedEvent([[maybe_unused]] const AZ::Vector3& translation)
{
OnSyncRewind();
}
void NetworkCharacterComponent::OnSyncRewind()
{
if (m_physicsCharacter == nullptr)
{
return;
}
const AZ::Vector3 currPosition = m_physicsCharacter->GetBasePosition();
if (!currPosition.IsClose(GetNetworkTransformComponent()->GetTranslation()))
{
uint32_t frameId = static_cast<uint32_t>(Multiplayer::GetNetworkTime()->GetHostFrameId());
m_physicsCharacter->SetFrameId(frameId);
//m_physicsCharacter->SetBasePosition(GetNetworkTransformComponent()->GetTranslation());
}
}
bool NetworkCharacterComponent::IsOnGround() const
{
auto pxController = static_cast<physx::PxController*>(m_physicsCharacter->GetNativePointer());
if (!pxController)
{
return true;
}
physx::PxControllerState state;
pxController->getState(state);
return state.touchedActor != nullptr || (state.collisionFlags & physx::PxControllerCollisionFlag::eCOLLISION_DOWN) != 0;
}
NetworkCharacterComponentController::NetworkCharacterComponentController(NetworkCharacterComponent& parent)
: NetworkCharacterComponentControllerBase(parent)
{
;
}
void NetworkCharacterComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
void NetworkCharacterComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
AZ::Vector3 NetworkCharacterComponentController::TryMoveWithVelocity(const AZ::Vector3& velocity, [[maybe_unused]] float deltaTime)
{
// Ensure any entities that we might interact with are properly synchronized to their rewind state
if (IsAuthority())
{
const AZ::Aabb entityStartBounds = AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->GetEntityLocalBoundsUnion(GetEntity()->GetId());
const AZ::Aabb entityFinalBounds = entityStartBounds.GetTranslated(velocity);
AZ::Aabb entitySweptBounds = entityStartBounds;
entitySweptBounds.AddAabb(entityFinalBounds);
Multiplayer::GetNetworkTime()->SyncEntitiesToRewindState(entitySweptBounds);
}
if ((GetParent().m_physicsCharacter == nullptr) || (velocity.GetLengthSq() <= 0.0f))
{
return GetEntity()->GetTransform()->GetWorldTranslation();
}
GetParent().m_physicsCharacter->AddVelocity(velocity);
GetParent().m_physicsCharacter->ApplyRequestedVelocity(deltaTime);
GetEntity()->GetTransform()->SetWorldTranslation(GetParent().m_physicsCharacter->GetBasePosition());
AZLOG
(
NET_Movement,
"Moved to position %f x %f x %f",
GetParent().m_physicsCharacter->GetBasePosition().GetX(),
GetParent().m_physicsCharacter->GetBasePosition().GetY(),
GetParent().m_physicsCharacter->GetBasePosition().GetZ()
);
return GetEntity()->GetTransform()->GetWorldTranslation();
}
}
@@ -0,0 +1,221 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Multiplayer/Components/NetworkHitVolumesComponent.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
#include <AzFramework/Physics/CharacterBus.h>
#include <AzFramework/Physics/Character.h>
#include <AzFramework/Physics/SystemBus.h>
#include <MCore/Source/AzCoreConversions.h>
#include <Integration/ActorComponentBus.h>
namespace Multiplayer
{
AZ_CVAR(bool, bg_DrawArticulatedHitVolumes, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables debug draw of articulated hit volumes");
AZ_CVAR(float, bg_DrawDebugHitVolumeLifetime, 0.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "The lifetime for hit volume draw-debug shapes");
AZ_CVAR(float, bg_RewindPositionTolerance, 0.0001f, nullptr, AZ::ConsoleFunctorFlags::Null, "Don't sync the physx entity if the square of delta position is less than this value");
AZ_CVAR(float, bg_RewindOrientationTolerance, 0.001f, nullptr, AZ::ConsoleFunctorFlags::Null, "Don't sync the physx entity if the square of delta orientation is less than this value");
NetworkHitVolumesComponent::AnimatedHitVolume::AnimatedHitVolume
(
AzNetworking::ConnectionId connectionId,
Physics::CharacterRequests* character,
const char* hitVolumeName,
const Physics::ColliderConfiguration* colliderConfig,
const Physics::ShapeConfiguration* shapeConfig,
const uint32_t jointIndex
)
: m_colliderConfig(colliderConfig)
, m_shapeConfig(shapeConfig)
, m_jointIndex(jointIndex)
{
m_transform.SetOwningConnectionId(connectionId);
m_colliderOffSetTransform = AZ::Transform::CreateFromQuaternionAndTranslation(m_colliderConfig->m_rotation, m_colliderConfig->m_position);
if (m_colliderConfig->m_isExclusive)
{
Physics::SystemRequestBus::BroadcastResult(m_physicsShape, &Physics::SystemRequests::CreateShape, *m_colliderConfig, *m_shapeConfig);
}
else
{
Physics::ColliderConfiguration colliderConfiguration = *m_colliderConfig;
colliderConfiguration.m_isExclusive = true;
colliderConfiguration.m_isSimulated = false;
colliderConfiguration.m_isInSceneQueries = true;
Physics::SystemRequestBus::BroadcastResult(m_physicsShape, &Physics::SystemRequests::CreateShape, colliderConfiguration, *m_shapeConfig);
}
if (m_physicsShape)
{
m_physicsShape->SetName(hitVolumeName);
character->GetCharacter()->AttachShape(m_physicsShape);
}
}
void NetworkHitVolumesComponent::AnimatedHitVolume::UpdateTransform(const AZ::Transform& transform)
{
m_transform = transform;
m_physicsShape->SetLocalPose(transform.GetTranslation(), transform.GetRotation());
}
void NetworkHitVolumesComponent::AnimatedHitVolume::SyncToCurrentTransform()
{
AZ::Transform rewoundTransform;
const AZ::Transform& targetTransform = m_transform.Get();
const float blendFactor = Multiplayer::GetNetworkTime()->GetHostBlendFactor();
if (blendFactor < 1.f)
{
// If a blend factor was supplied, interpolate the transform appropriately
const AZ::Transform& previousTransform = m_transform.GetPrevious();
rewoundTransform.SetRotation(previousTransform.GetRotation().Slerp(targetTransform.GetRotation(), blendFactor));
rewoundTransform.SetTranslation(previousTransform.GetTranslation().Lerp(targetTransform.GetTranslation(), blendFactor));
rewoundTransform.SetUniformScale(AZ::Lerp(previousTransform.GetUniformScale(), targetTransform.GetUniformScale(), blendFactor));
}
else
{
rewoundTransform = m_transform.Get();
}
const AZ::Transform physicsTransform = AZ::Transform::CreateFromQuaternionAndTranslation(m_physicsShape->GetLocalPose().second, m_physicsShape->GetLocalPose().first);
// Don't call SetLocalPose unless the transforms are actually different
const AZ::Vector3 positionDelta = physicsTransform.GetTranslation() - rewoundTransform.GetTranslation();
const AZ::Quaternion orientationDelta = physicsTransform.GetRotation() - rewoundTransform.GetRotation();
if ((positionDelta.GetLengthSq() >= bg_RewindPositionTolerance) || (orientationDelta.GetLengthSq() >= bg_RewindOrientationTolerance))
{
m_physicsShape->SetLocalPose(rewoundTransform.GetTranslation(), rewoundTransform.GetRotation());
}
}
void NetworkHitVolumesComponent::NetworkHitVolumesComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<NetworkHitVolumesComponent, NetworkHitVolumesComponentBase>()
->Version(1);
}
NetworkHitVolumesComponentBase::Reflect(context);
}
NetworkHitVolumesComponent::NetworkHitVolumesComponent()
: m_syncRewindHandler([this]() { OnSyncRewind(); })
, m_preRenderHandler([this](float deltaTime) { OnPreRender(deltaTime); })
, m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform& worldTm) { OnTransformUpdate(worldTm); })
{
;
}
void NetworkHitVolumesComponent::OnInit()
{
;
}
void NetworkHitVolumesComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
EMotionFX::Integration::ActorComponentNotificationBus::Handler::BusConnect(GetEntityId());
GetNetBindComponent()->AddEntitySyncRewindEventHandler(m_syncRewindHandler);
m_physicsCharacter = Physics::CharacterRequestBus::FindFirstHandler(GetEntityId());
GetTransformComponent()->BindTransformChangedEventHandler(m_transformChangedHandler);
OnTransformUpdate(GetTransformComponent()->GetWorldTM());
}
void NetworkHitVolumesComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
DestroyHitVolumes();
EMotionFX::Integration::ActorComponentNotificationBus::Handler::BusDisconnect();
}
void NetworkHitVolumesComponent::OnPreRender([[maybe_unused]] float deltaTime)
{
if (m_animatedHitVolumes.size() <= 0)
{
CreateHitVolumes();
}
AZ::Vector3 position, scale;
AZ::Quaternion rotation;
for (AnimatedHitVolume& hitVolume : m_animatedHitVolumes)
{
m_actorComponent->GetJointTransformComponents(hitVolume.m_jointIndex, EMotionFX::Integration::Space::ModelSpace, position, rotation, scale);
hitVolume.UpdateTransform(AZ::Transform::CreateFromQuaternionAndTranslation(rotation, position) * hitVolume.m_colliderOffSetTransform);
}
}
void NetworkHitVolumesComponent::OnTransformUpdate([[maybe_unused]] const AZ::Transform& transform)
{
OnSyncRewind();
}
void NetworkHitVolumesComponent::OnSyncRewind()
{
if (m_physicsCharacter && m_physicsCharacter->GetCharacter())
{
uint32_t frameId = static_cast<uint32_t>(Multiplayer::GetNetworkTime()->GetHostFrameId());
m_physicsCharacter->GetCharacter()->SetFrameId(frameId);
}
for (AnimatedHitVolume& hitVolume : m_animatedHitVolumes)
{
hitVolume.SyncToCurrentTransform();
}
}
void NetworkHitVolumesComponent::CreateHitVolumes()
{
if (m_physicsCharacter == nullptr || m_actorComponent == nullptr)
{
return;
}
const Physics::AnimationConfiguration* physicsConfig = m_actorComponent->GetPhysicsConfig();
if (physicsConfig == nullptr)
{
return;
}
m_hitDetectionConfig = &physicsConfig->m_hitDetectionConfig;
const AzNetworking::ConnectionId owningConnectionId = GetNetBindComponent()->GetOwningConnectionId();
m_animatedHitVolumes.reserve(m_hitDetectionConfig->m_nodes.size());
for (const Physics::CharacterColliderNodeConfiguration& nodeConfig : m_hitDetectionConfig->m_nodes)
{
const AZStd::size_t jointIndex = m_actorComponent->GetJointIndexByName(nodeConfig.m_name.c_str());
if (jointIndex == EMotionFX::Integration::ActorComponentRequests::s_invalidJointIndex)
{
continue;
}
for (const AzPhysics::ShapeColliderPair& coliderPair : nodeConfig.m_shapes)
{
const Physics::ColliderConfiguration* colliderConfig = coliderPair.first.get();
Physics::ShapeConfiguration* shapeConfig = coliderPair.second.get();
m_animatedHitVolumes.emplace_back(owningConnectionId, m_physicsCharacter, nodeConfig.m_name.c_str(), colliderConfig, shapeConfig, aznumeric_cast<uint32_t>(jointIndex));
}
}
}
void NetworkHitVolumesComponent::DestroyHitVolumes()
{
m_animatedHitVolumes.clear();
}
void NetworkHitVolumesComponent::OnActorInstanceCreated([[maybe_unused]] EMotionFX::ActorInstance* actorInstance)
{
m_actorComponent = EMotionFX::Integration::ActorComponentRequestBus::FindFirstHandler(GetEntity()->GetId());
}
void NetworkHitVolumesComponent::OnActorInstanceDestroyed([[maybe_unused]] EMotionFX::ActorInstance* actorInstance)
{
m_actorComponent = nullptr;
}
}
@@ -0,0 +1,147 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Multiplayer/Components/NetworkRigidBodyComponent.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Physics/RigidBodyBus.h>
#include <AzFramework/Physics/SimulatedBodies/RigidBody.h>
namespace Multiplayer
{
AZ_CVAR_EXTERNED(float, bg_RewindPositionTolerance);
AZ_CVAR_EXTERNED(float, bg_RewindOrientationTolerance);
void NetworkRigidBodyComponent::NetworkRigidBodyComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<NetworkRigidBodyComponent, NetworkRigidBodyComponentBase>()->Version(1);
}
NetworkRigidBodyComponentBase::Reflect(context);
}
void NetworkRigidBodyComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("NetworkRigidBodyService"));
}
void NetworkRigidBodyComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("PhysXRigidBodyService"));
}
void NetworkRigidBodyComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC_CE("TransformService"));
dependent.push_back(AZ_CRC_CE("PhysXRigidBodyService"));
}
NetworkRigidBodyComponent::NetworkRigidBodyComponent()
: m_syncRewindHandler([this](){ OnSyncRewind(); })
, m_transformChangedHandler([this]([[maybe_unused]] const AZ::Transform& localTm, const AZ::Transform& worldTm){ OnTransformUpdate(worldTm); })
{
}
void NetworkRigidBodyComponent::OnInit()
{
}
void NetworkRigidBodyComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
NetworkRigidBodyRequestBus::Handler::BusConnect(GetEntityId());
GetNetBindComponent()->AddEntitySyncRewindEventHandler(m_syncRewindHandler);
GetEntity()->FindComponent<AzFramework::TransformComponent>()->BindTransformChangedEventHandler(m_transformChangedHandler);
m_physicsRigidBodyComponent =
Physics::RigidBodyRequestBus::FindFirstHandler(GetEntity()->GetId());
AZ_Assert(m_physicsRigidBodyComponent, "PhysX Rigid Body Component is required on entity %s", GetEntity()->GetName().c_str());
if (!HasController())
{
m_physicsRigidBodyComponent->SetKinematic(true);
}
}
void NetworkRigidBodyComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
NetworkRigidBodyRequestBus::Handler::BusDisconnect();
}
void NetworkRigidBodyComponent::OnTransformUpdate(const AZ::Transform& worldTm)
{
m_transform = worldTm;
if (!HasController())
{
m_physicsRigidBodyComponent->SetKinematicTarget(worldTm);
}
}
void NetworkRigidBodyComponent::OnSyncRewind()
{
uint32_t frameId = static_cast<uint32_t>(Multiplayer::GetNetworkTime()->GetHostFrameId());
AzPhysics::RigidBody* rigidBody = m_physicsRigidBodyComponent->GetRigidBody();
rigidBody->SetFrameId(frameId);
AZ::Transform rewoundTransform;
const AZ::Transform& targetTransform = m_transform.Get();
const float blendFactor = Multiplayer::GetNetworkTime()->GetHostBlendFactor();
if (blendFactor < 1.f)
{
// If a blend factor was supplied, interpolate the transform appropriately
const AZ::Transform& previousTransform = m_transform.GetPrevious();
rewoundTransform.SetRotation(previousTransform.GetRotation().Slerp(targetTransform.GetRotation(), blendFactor));
rewoundTransform.SetTranslation(previousTransform.GetTranslation().Lerp(targetTransform.GetTranslation(), blendFactor));
rewoundTransform.SetUniformScale(AZ::Lerp(previousTransform.GetUniformScale(), targetTransform.GetUniformScale(), blendFactor));
}
else
{
rewoundTransform = m_transform.Get();
}
const AZ::Transform& physicsTransform = rigidBody->GetTransform();
// Don't call SetLocalPose unless the transforms are actually different
const AZ::Vector3 positionDelta = physicsTransform.GetTranslation() - rewoundTransform.GetTranslation();
const AZ::Quaternion orientationDelta = physicsTransform.GetRotation() - rewoundTransform.GetRotation();
if ((positionDelta.GetLengthSq() >= bg_RewindPositionTolerance) ||
(orientationDelta.GetLengthSq() >= bg_RewindOrientationTolerance))
{
rigidBody->SetTransform(rewoundTransform);
}
}
NetworkRigidBodyComponentController::NetworkRigidBodyComponentController(NetworkRigidBodyComponent& parent)
: NetworkRigidBodyComponentControllerBase(parent)
{
;
}
void NetworkRigidBodyComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
void NetworkRigidBodyComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
void NetworkRigidBodyComponentController::HandleSendApplyImpulse
(
[[maybe_unused]] AzNetworking::IConnection* invokingConnection,
const AZ::Vector3& impulse,
const AZ::Vector3& worldPoint
)
{
AzPhysics::RigidBody* rigidBody = GetParent().m_physicsRigidBodyComponent->GetRigidBody();
rigidBody->ApplyLinearImpulseAtWorldPoint(impulse, worldPoint);
}
} // namespace Multiplayer
@@ -18,6 +18,9 @@ set(FILES
Include/Multiplayer/Components/MultiplayerController.h
Include/Multiplayer/Components/MultiplayerComponentRegistry.h
Include/Multiplayer/Components/NetBindComponent.h
Include/Multiplayer/Components/NetworkCharacterComponent.h
Include/Multiplayer/Components/NetworkHitVolumesComponent.h
Include/Multiplayer/Components/NetworkRigidBodyComponent.h
Include/Multiplayer/Components/NetworkTransformComponent.h
Include/Multiplayer/ConnectionData/IConnectionData.h
Include/Multiplayer/EntityDomains/IEntityDomain.h
@@ -51,12 +54,18 @@ set(FILES
Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml
Source/AutoGen/Multiplayer.AutoPackets.xml
Source/AutoGen/MultiplayerEditor.AutoPackets.xml
Source/AutoGen/NetworkCharacterComponent.AutoComponent.xml
Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml
Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml
Source/AutoGen/NetworkTransformComponent.AutoComponent.xml
Source/Components/LocalPredictionPlayerInputComponent.cpp
Source/Components/MultiplayerComponent.cpp
Source/Components/MultiplayerController.cpp
Source/Components/MultiplayerComponentRegistry.cpp
Source/Components/NetBindComponent.cpp
Source/Components/NetworkCharacterComponent.cpp
Source/Components/NetworkHitVolumesComponent.cpp
Source/Components/NetworkRigidBodyComponent.cpp
Source/Components/NetworkTransformComponent.cpp
Source/ConnectionData/ClientToServerConnectionData.cpp
Source/ConnectionData/ClientToServerConnectionData.h