Merge branch 'development' into Prefabs/SpawnableEntityAlias

This commit is contained in:
AMZN-koppersr
2021-11-08 10:10:06 -08:00
74 changed files with 1239 additions and 1794 deletions
+1
View File
@@ -12,6 +12,7 @@ Editor/EditorEventLog.xml
Editor/EditorLayout.xml
**/*egg-info/**
**/*egg-link
**/[Rr]estricted
UserSettings.xml
[Uu]ser/
FrameCapture/**
@@ -20,19 +20,6 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED)
COMPONENT
Atom
)
ly_add_pytest(
NAME AutomatedTesting::Atom_TestSuite_Main_Optimized
TEST_SUITE main
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_Optimized.py
TEST_SERIAL
TIMEOUT 600
RUNTIME_DEPENDENCIES
AssetProcessor
AutomatedTesting.Assets
Editor
COMPONENT
Atom
)
ly_add_pytest(
NAME AutomatedTesting::Atom_TestSuite_Sandbox
TEST_SUITE sandbox
@@ -4,252 +4,95 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import logging
import os
import pytest
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
from Atom.atom_utils.atom_constants import LIGHT_TYPES
logger = logging.getLogger(__name__)
TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests")
from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("level", ["auto_test"])
class TestAtomEditorComponentsMain(object):
"""Holds tests for Atom components."""
class TestAutomation(EditorTestSuite):
@pytest.mark.test_case_id("C32078118") # Decal
@pytest.mark.test_case_id("C32078119") # DepthOfField
@pytest.mark.test_case_id("C32078120") # Directional Light
@pytest.mark.test_case_id("C32078121") # Exposure Control
@pytest.mark.test_case_id("C32078115") # Global Skylight (IBL)
@pytest.mark.test_case_id("C32078125") # Physical Sky
@pytest.mark.test_case_id("C32078127") # PostFX Layer
@pytest.mark.test_case_id("C32078131") # PostFX Radius Weight Modifier
@pytest.mark.test_case_id("C32078117") # Light
@pytest.mark.test_case_id("C36525660") # Display Mapper
def test_AtomEditorComponents_AddedToEntity(self, request, editor, level, workspace, project, launcher_platform):
"""
Please review the hydra script run by this test for more specific test info.
Tests the Atom components & verifies all "expected_lines" appear in Editor.log
"""
cfg_args = [level]
@pytest.mark.test_case_id("C36525657")
class AtomEditorComponents_BloomAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_BloomAdded as test_module
expected_lines = [
# Decal Component
"Decal Entity successfully created",
"Decal_test: Component added to the entity: True",
"Decal_test: Component removed after UNDO: True",
"Decal_test: Component added after REDO: True",
"Decal_test: Entered game mode: True",
"Decal_test: Exit game mode: True",
"Decal Controller|Configuration|Material: SUCCESS",
"Decal_test: Entity is hidden: True",
"Decal_test: Entity is shown: True",
"Decal_test: Entity deleted: True",
"Decal_test: UNDO entity deletion works: True",
"Decal_test: REDO entity deletion works: True",
# DepthOfField Component
"DepthOfField Entity successfully created",
"DepthOfField_test: Component added to the entity: True",
"DepthOfField_test: Component removed after UNDO: True",
"DepthOfField_test: Component added after REDO: True",
"DepthOfField_test: Entered game mode: True",
"DepthOfField_test: Exit game mode: True",
"DepthOfField_test: Entity disabled initially: True",
"DepthOfField_test: Entity enabled after adding required components: True",
"DepthOfField Controller|Configuration|Camera Entity: SUCCESS",
"DepthOfField_test: Entity is hidden: True",
"DepthOfField_test: Entity is shown: True",
"DepthOfField_test: Entity deleted: True",
"DepthOfField_test: UNDO entity deletion works: True",
"DepthOfField_test: REDO entity deletion works: True",
# Directional Light Component
"Directional Light Entity successfully created",
"Directional Light_test: Component added to the entity: True",
"Directional Light_test: Component removed after UNDO: True",
"Directional Light_test: Component added after REDO: True",
"Directional Light_test: Entered game mode: True",
"Directional Light_test: Exit game mode: True",
"Directional Light_test: Entity is hidden: True",
"Directional Light_test: Entity is shown: True",
"Directional Light_test: Entity deleted: True",
"Directional Light_test: UNDO entity deletion works: True",
"Directional Light_test: REDO entity deletion works: True",
# Exposure Control Component
"Exposure Control Entity successfully created",
"Exposure Control_test: Component added to the entity: True",
"Exposure Control_test: Component removed after UNDO: True",
"Exposure Control_test: Component added after REDO: True",
"Exposure Control_test: Entered game mode: True",
"Exposure Control_test: Exit game mode: True",
"Exposure Control_test: Entity disabled initially: True",
"Exposure Control_test: Entity enabled after adding required components: True",
"Exposure Control_test: Entity is hidden: True",
"Exposure Control_test: Entity is shown: True",
"Exposure Control_test: Entity deleted: True",
"Exposure Control_test: UNDO entity deletion works: True",
"Exposure Control_test: REDO entity deletion works: True",
# Global Skylight (IBL) Component
"Global Skylight (IBL) Entity successfully created",
"Global Skylight (IBL)_test: Component added to the entity: True",
"Global Skylight (IBL)_test: Component removed after UNDO: True",
"Global Skylight (IBL)_test: Component added after REDO: True",
"Global Skylight (IBL)_test: Entered game mode: True",
"Global Skylight (IBL)_test: Exit game mode: True",
"Global Skylight (IBL) Controller|Configuration|Diffuse Image: SUCCESS",
"Global Skylight (IBL) Controller|Configuration|Specular Image: SUCCESS",
"Global Skylight (IBL)_test: Entity is hidden: True",
"Global Skylight (IBL)_test: Entity is shown: True",
"Global Skylight (IBL)_test: Entity deleted: True",
"Global Skylight (IBL)_test: UNDO entity deletion works: True",
"Global Skylight (IBL)_test: REDO entity deletion works: True",
# Physical Sky Component
"Physical Sky Entity successfully created",
"Physical Sky component was added to entity",
"Entity has a Physical Sky component",
"Physical Sky_test: Component added to the entity: True",
"Physical Sky_test: Component removed after UNDO: True",
"Physical Sky_test: Component added after REDO: True",
"Physical Sky_test: Entered game mode: True",
"Physical Sky_test: Exit game mode: True",
"Physical Sky_test: Entity is hidden: True",
"Physical Sky_test: Entity is shown: True",
"Physical Sky_test: Entity deleted: True",
"Physical Sky_test: UNDO entity deletion works: True",
"Physical Sky_test: REDO entity deletion works: True",
# PostFX Layer Component
"PostFX Layer Entity successfully created",
"PostFX Layer_test: Component added to the entity: True",
"PostFX Layer_test: Component removed after UNDO: True",
"PostFX Layer_test: Component added after REDO: True",
"PostFX Layer_test: Entered game mode: True",
"PostFX Layer_test: Exit game mode: True",
"PostFX Layer_test: Entity is hidden: True",
"PostFX Layer_test: Entity is shown: True",
"PostFX Layer_test: Entity deleted: True",
"PostFX Layer_test: UNDO entity deletion works: True",
"PostFX Layer_test: REDO entity deletion works: True",
# PostFX Radius Weight Modifier Component
"PostFX Radius Weight Modifier Entity successfully created",
"PostFX Radius Weight Modifier_test: Component added to the entity: True",
"PostFX Radius Weight Modifier_test: Component removed after UNDO: True",
"PostFX Radius Weight Modifier_test: Component added after REDO: True",
"PostFX Radius Weight Modifier_test: Entered game mode: True",
"PostFX Radius Weight Modifier_test: Exit game mode: True",
"PostFX Radius Weight Modifier_test: Entity is hidden: True",
"PostFX Radius Weight Modifier_test: Entity is shown: True",
"PostFX Radius Weight Modifier_test: Entity deleted: True",
"PostFX Radius Weight Modifier_test: UNDO entity deletion works: True",
"PostFX Radius Weight Modifier_test: REDO entity deletion works: True",
# Light Component
"Light Entity successfully created",
"Light_test: Component added to the entity: True",
"Light_test: Component removed after UNDO: True",
"Light_test: Component added after REDO: True",
"Light_test: Entered game mode: True",
"Light_test: Exit game mode: True",
"Light_test: Entity is hidden: True",
"Light_test: Entity is shown: True",
"Light_test: Entity deleted: True",
"Light_test: UNDO entity deletion works: True",
"Light_test: REDO entity deletion works: True",
# Display Mapper Component
"Display Mapper Entity successfully created",
"Display Mapper_test: Component added to the entity: True",
"Display Mapper_test: Component removed after UNDO: True",
"Display Mapper_test: Component added after REDO: True",
"Display Mapper_test: Entered game mode: True",
"Display Mapper_test: Exit game mode: True",
"Display Mapper_test: Entity is hidden: True",
"Display Mapper_test: Entity is shown: True",
"Display Mapper_test: Entity deleted: True",
"Display Mapper_test: UNDO entity deletion works: True",
"Display Mapper_test: REDO entity deletion works: True",
]
@pytest.mark.test_case_id("C32078118")
class AtomEditorComponents_DecalAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DecalAdded as test_module
unexpected_lines = [
"Trace::Assert",
"Trace::Error",
"Traceback (most recent call last):",
]
@pytest.mark.test_case_id("C36525658")
class AtomEditorComponents_DeferredFogAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DeferredFogAdded as test_module
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_AtomEditorComponents_AddedToEntity.py",
timeout=120,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
null_renderer=True,
cfg_args=cfg_args,
)
@pytest.mark.test_case_id("C32078119")
class AtomEditorComponents_DepthOfFieldAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DepthOfFieldAdded as test_module
@pytest.mark.test_case_id("C34525095")
def test_AtomEditorComponents_LightComponent(
self, request, editor, workspace, project, launcher_platform, level):
"""
Please review the hydra script run by this test for more specific test info.
Tests that the Light component has the expected property options available to it.
"""
cfg_args = [level]
@pytest.mark.test_case_id("C32078120")
class AtomEditorComponents_DirectionalLightAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DirectionalLightAdded as test_module
expected_lines = [
"light_entity Entity successfully created",
"Entity has a Light component",
"light_entity_test: Component added to the entity: True",
f"light_entity_test: Property value is {LIGHT_TYPES['sphere']} which matches {LIGHT_TYPES['sphere']}",
"Controller|Configuration|Shadows|Enable shadow set to True",
"light_entity Controller|Configuration|Shadows|Shadowmap size: SUCCESS",
"Controller|Configuration|Shadows|Shadow filter method set to 1", # PCF
"Controller|Configuration|Shadows|Filtering sample count set to 4",
"Controller|Configuration|Shadows|Filtering sample count set to 64",
"Controller|Configuration|Shadows|Shadow filter method set to 2", # ESM
"Controller|Configuration|Shadows|ESM exponent set to 50.0",
"Controller|Configuration|Shadows|ESM exponent set to 5000.0",
"Controller|Configuration|Shadows|Shadow filter method set to 3", # ESM+PCF
f"light_entity_test: Property value is {LIGHT_TYPES['spot_disk']} which matches {LIGHT_TYPES['spot_disk']}",
f"light_entity_test: Property value is {LIGHT_TYPES['capsule']} which matches {LIGHT_TYPES['capsule']}",
f"light_entity_test: Property value is {LIGHT_TYPES['quad']} which matches {LIGHT_TYPES['quad']}",
"light_entity Controller|Configuration|Fast approximation: SUCCESS",
"light_entity Controller|Configuration|Both directions: SUCCESS",
f"light_entity_test: Property value is {LIGHT_TYPES['polygon']} which matches {LIGHT_TYPES['polygon']}",
f"light_entity_test: Property value is {LIGHT_TYPES['simple_point']} "
f"which matches {LIGHT_TYPES['simple_point']}",
"Controller|Configuration|Attenuation radius|Mode set to 0",
"Controller|Configuration|Attenuation radius|Radius set to 100.0",
f"light_entity_test: Property value is {LIGHT_TYPES['simple_spot']} "
f"which matches {LIGHT_TYPES['simple_spot']}",
"Controller|Configuration|Shutters|Outer angle set to 45.0",
"Controller|Configuration|Shutters|Outer angle set to 90.0",
"light_entity_test: Component added to the entity: True",
"Light component test (non-GPU) completed.",
]
@pytest.mark.test_case_id("C36525660")
class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DisplayMapperAdded as test_module
unexpected_lines = [
"Trace::Assert",
"Trace::Error",
"Traceback (most recent call last):",
]
@pytest.mark.test_case_id("C32078121")
class AtomEditorComponents_ExposureControlAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_ExposureControlAdded as test_module
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_AtomEditorComponents_LightComponent.py",
timeout=120,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
null_renderer=True,
cfg_args=cfg_args,
)
@pytest.mark.test_case_id("C32078115")
class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module
@pytest.mark.test_case_id("C32078122")
class AtomEditorComponents_GridAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_GridAdded as test_module
@pytest.mark.test_case_id("C36525671")
class AtomEditorComponents_HDRColorGradingAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_HDRColorGradingAdded as test_module
@pytest.mark.test_case_id("C32078117")
class AtomEditorComponents_LightAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_LightAdded as test_module
@pytest.mark.test_case_id("C32078123")
class AtomEditorComponents_MaterialAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_MaterialAdded as test_module
@pytest.mark.test_case_id("C32078124")
class AtomEditorComponents_MeshAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_MeshAdded as test_module
@pytest.mark.test_case_id("C36525663")
class AtomEditorComponents_OcclusionCullingPlaneAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_OcclusionCullingPlaneAdded as test_module
@pytest.mark.test_case_id("C32078125")
class AtomEditorComponents_PhysicalSkyAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PhysicalSkyAdded as test_module
@pytest.mark.test_case_id("C36525664")
class AtomEditorComponents_PostFXGradientWeightModifierAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded as test_module
@pytest.mark.test_case_id("C32078127")
class AtomEditorComponents_PostFXLayerAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFXLayerAdded as test_module
@pytest.mark.test_case_id("C32078131")
class AtomEditorComponents_PostFXRadiusWeightModifierAdded(EditorSharedTest):
from Atom.tests import (
hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded as test_module)
@pytest.mark.test_case_id("C36525665")
class AtomEditorComponents_PostFXShapeWeightModifierAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded as test_module
@pytest.mark.test_case_id("C32078128")
class AtomEditorComponents_ReflectionProbeAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_ReflectionProbeAdded as test_module
class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest):
from Atom.tests import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module
@@ -1,91 +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
"""
import pytest
from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestAutomation(EditorTestSuite):
@pytest.mark.test_case_id("C32078118")
class AtomEditorComponents_DecalAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DecalAdded as test_module
@pytest.mark.test_case_id("C32078119")
class AtomEditorComponents_DepthOfFieldAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DepthOfFieldAdded as test_module
@pytest.mark.test_case_id("C32078120")
class AtomEditorComponents_DirectionalLightAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DirectionalLightAdded as test_module
@pytest.mark.test_case_id("C36525660")
class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_DisplayMapperAdded as test_module
@pytest.mark.test_case_id("C32078121")
class AtomEditorComponents_ExposureControlAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_ExposureControlAdded as test_module
@pytest.mark.test_case_id("C32078115")
class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module
@pytest.mark.test_case_id("C32078122")
class AtomEditorComponents_GridAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_GridAdded as test_module
@pytest.mark.test_case_id("C36525671")
class AtomEditorComponents_HDRColorGradingAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_HDRColorGradingAdded as test_module
@pytest.mark.test_case_id("C32078117")
class AtomEditorComponents_LightAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_LightAdded as test_module
@pytest.mark.test_case_id("C32078123")
class AtomEditorComponents_MaterialAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_MaterialAdded as test_module
@pytest.mark.test_case_id("C32078124")
class AtomEditorComponents_MeshAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_MeshAdded as test_module
@pytest.mark.test_case_id("C36525663")
class AtomEditorComponents_OcclusionCullingPlaneAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_OcclusionCullingPlaneAdded as test_module
@pytest.mark.test_case_id("C32078125")
class AtomEditorComponents_PhysicalSkyAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PhysicalSkyAdded as test_module
@pytest.mark.test_case_id("C36525664")
class AtomEditorComponents_PostFXGradientWeightModifierAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded as test_module
@pytest.mark.test_case_id("C32078127")
class AtomEditorComponents_PostFXLayerAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFXLayerAdded as test_module
@pytest.mark.test_case_id("C32078131")
class AtomEditorComponents_PostFXRadiusWeightModifierAdded(EditorSharedTest):
from Atom.tests import (
hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded as test_module)
@pytest.mark.test_case_id("C36525665")
class AtomEditorComponents_PostFXShapeWeightModifierAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded as test_module
@pytest.mark.test_case_id("C32078128")
class AtomEditorComponents_ReflectionProbeAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_ReflectionProbeAdded as test_module
class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest):
from Atom.tests import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module
@@ -9,63 +9,84 @@ import os
import pytest
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
from Atom.atom_utils.atom_constants import LIGHT_TYPES
logger = logging.getLogger(__name__)
TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests")
class TestAtomEditorComponentsSandbox(object):
# It requires at least one test
def test_Dummy(self, request, editor, level, workspace, project, launcher_platform):
pass
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("level", ["auto_test"])
class TestAtomEditorComponentsMain(object):
"""Holds tests for Atom components."""
@pytest.mark.test_case_id("C32078128")
def test_AtomEditorComponents_ReflectionProbeAddedToEntity(
self, request, editor, level, workspace, project, launcher_platform):
"""
Please review the hydra script run by this test for more specific test info.
Tests the following Atom components and verifies all "expected_lines" appear in Editor.log:
1. Reflection Probe
"""
cfg_args = [level]
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("level", ["auto_test"])
class TestAtomEditorComponentsMain(object):
"""Holds tests for Atom components."""
expected_lines = [
# Reflection Probe Component
"Reflection Probe Entity successfully created",
"Reflection Probe_test: Component added to the entity: True",
"Reflection Probe_test: Component removed after UNDO: True",
"Reflection Probe_test: Component added after REDO: True",
"Reflection Probe_test: Entered game mode: True",
"Reflection Probe_test: Exit game mode: True",
"Reflection Probe_test: Entity disabled initially: True",
"Reflection Probe_test: Entity enabled after adding required components: True",
"Reflection Probe_test: Cubemap is generated: True",
"Reflection Probe_test: Entity is hidden: True",
"Reflection Probe_test: Entity is shown: True",
"Reflection Probe_test: Entity deleted: True",
"Reflection Probe_test: UNDO entity deletion works: True",
"Reflection Probe_test: REDO entity deletion works: True",
]
@pytest.mark.test_case_id("C34525095")
def test_AtomEditorComponents_LightComponent(
self, request, editor, workspace, project, launcher_platform, level):
"""
Please review the hydra script run by this test for more specific test info.
Tests that the Light component has the expected property options available to it.
"""
cfg_args = [level]
expected_lines = [
"light_entity Entity successfully created",
"Entity has a Light component",
"light_entity_test: Component added to the entity: True",
f"light_entity_test: Property value is {LIGHT_TYPES['sphere']} which matches {LIGHT_TYPES['sphere']}",
"Controller|Configuration|Shadows|Enable shadow set to True",
"light_entity Controller|Configuration|Shadows|Shadowmap size: SUCCESS",
"Controller|Configuration|Shadows|Shadow filter method set to 1", # PCF
"Controller|Configuration|Shadows|Filtering sample count set to 4",
"Controller|Configuration|Shadows|Filtering sample count set to 64",
"Controller|Configuration|Shadows|Shadow filter method set to 2", # ESM
"Controller|Configuration|Shadows|ESM exponent set to 50.0",
"Controller|Configuration|Shadows|ESM exponent set to 5000.0",
"Controller|Configuration|Shadows|Shadow filter method set to 3", # ESM+PCF
f"light_entity_test: Property value is {LIGHT_TYPES['spot_disk']} which matches {LIGHT_TYPES['spot_disk']}",
f"light_entity_test: Property value is {LIGHT_TYPES['capsule']} which matches {LIGHT_TYPES['capsule']}",
f"light_entity_test: Property value is {LIGHT_TYPES['quad']} which matches {LIGHT_TYPES['quad']}",
"light_entity Controller|Configuration|Fast approximation: SUCCESS",
"light_entity Controller|Configuration|Both directions: SUCCESS",
f"light_entity_test: Property value is {LIGHT_TYPES['polygon']} which matches {LIGHT_TYPES['polygon']}",
f"light_entity_test: Property value is {LIGHT_TYPES['simple_point']} "
f"which matches {LIGHT_TYPES['simple_point']}",
"Controller|Configuration|Attenuation radius|Mode set to 0",
"Controller|Configuration|Attenuation radius|Radius set to 100.0",
f"light_entity_test: Property value is {LIGHT_TYPES['simple_spot']} "
f"which matches {LIGHT_TYPES['simple_spot']}",
"Controller|Configuration|Shutters|Outer angle set to 45.0",
"Controller|Configuration|Shutters|Outer angle set to 90.0",
"light_entity_test: Component added to the entity: True",
"Light component test (non-GPU) completed.",
]
unexpected_lines = ["Traceback (most recent call last):"]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_AtomEditorComponents_LightComponent.py",
timeout=120,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
null_renderer=True,
cfg_args=cfg_args,
)
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_AtomEditorComponents_AddedToEntity.py",
timeout=120,
expected_lines=expected_lines,
unexpected_lines=[],
halt_on_unexpected=True,
null_renderer=True,
cfg_args=cfg_args,
)
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_generic'])
@@ -119,8 +140,6 @@ class TestMaterialEditorBasicTests(object):
"Save All worked as expected: True",
]
unexpected_lines = [
# "Trace::Assert",
# "Trace::Error",
"Traceback (most recent call last):"
]
@@ -42,12 +42,14 @@ class AtomComponentProperties:
Bloom component properties. Requires PostFX Layer component.
- 'requires' a list of component names as strings required by this component.
Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n
- 'Enable Bloom' Toggle active state of the component True/False
:param property: From the last element of the property tree path. Default 'name' for component name string.
:return: Full property path OR component name if no property specified.
"""
properties = {
'name': 'Bloom',
'requires': [AtomComponentProperties.postfx_layer()],
'Enable Bloom': 'Controller|Configuration|Enable Bloom',
}
return properties[property]
@@ -83,12 +85,14 @@ class AtomComponentProperties:
Deferred Fog component properties. Requires PostFX Layer component.
- 'requires' a list of component names as strings required by this component.
Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n
- 'Enable Deferred Fog' Toggle active state of the component True/False
:param property: From the last element of the property tree path. Default 'name' for component name string.
:return: Full property path OR component name if no property specified.
"""
properties = {
'name': 'Deferred Fog',
'requires': [AtomComponentProperties.postfx_layer()],
'Enable Deferred Fog': 'Controller|Configuration|Enable Deferred Fog',
}
return properties[property]
@@ -212,6 +216,7 @@ class AtomComponentProperties:
HDR Color Grading component properties. Requires PostFX Layer component.
- 'requires' a list of component names as strings required by this component.
Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n
- 'Enable HDR color grading' Toggle active state of the component True/False
:param property: From the last element of the property tree path. Default 'name' for component name string.
:return: Full property path OR component name if no property specified.
"""
@@ -1,238 +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
"""
import os
import sys
import azlmbr.math as math
import azlmbr.bus as bus
import azlmbr.paths
import azlmbr.asset as asset
import azlmbr.entity as entity
import azlmbr.legacy.general as general
import azlmbr.editor as editor
import azlmbr.render as render
sys.path.append(os.path.join(azlmbr.paths.projectroot, "Gem", "PythonTests"))
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.utils import TestHelper
def run():
"""
Summary:
The below common tests are done for each of the components.
1) Addition of component to the entity
2) UNDO/REDO of addition of component
3) Enter/Exit game mode
4) Hide/Show entity containing component
5) Deletion of component
6) UNDO/REDO of deletion of component
Some additional tests for specific components include
1) Assigning value to some properties of each component
2) Verifying if the component is activated only when the required components are added
Expected Result:
1) Component can be added to an entity.
2) The addition of component can be undone and redone.
3) Game mode can be entered/exited without issue.
4) Entity with component can be hidden/shown.
5) Component can be deleted.
6) The deletion of component can be undone and redone.
7) Component is activated only when the required components are added
8) Values can be assigned to the properties of the component
:return: None
"""
def create_entity_undo_redo_component_addition(component_name):
new_entity = hydra.Entity(f"{component_name}")
new_entity.create_entity(math.Vector3(512.0, 512.0, 34.0), [component_name])
general.log(f"{component_name}_test: Component added to the entity: "
f"{hydra.has_components(new_entity.id, [component_name])}")
# undo component addition
general.undo()
TestHelper.wait_for_condition(lambda: not hydra.has_components(new_entity.id, [component_name]), 2.0)
general.log(f"{component_name}_test: Component removed after UNDO: "
f"{not hydra.has_components(new_entity.id, [component_name])}")
# redo component addition
general.redo()
TestHelper.wait_for_condition(lambda: hydra.has_components(new_entity.id, [component_name]), 2.0)
general.log(f"{component_name}_test: Component added after REDO: "
f"{hydra.has_components(new_entity.id, [component_name])}")
return new_entity
def verify_enter_exit_game_mode(component_name):
general.enter_game_mode()
TestHelper.wait_for_condition(lambda: general.is_in_game_mode(), 2.0)
general.log(f"{component_name}_test: Entered game mode: {general.is_in_game_mode()}")
general.exit_game_mode()
TestHelper.wait_for_condition(lambda: not general.is_in_game_mode(), 2.0)
general.log(f"{component_name}_test: Exit game mode: {not general.is_in_game_mode()}")
def verify_hide_unhide_entity(component_name, entity_obj):
def is_entity_hidden(entity_id):
return editor.EditorEntityInfoRequestBus(bus.Event, "IsHidden", entity_id)
editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", entity_obj.id, False)
general.idle_wait_frames(1)
general.log(f"{component_name}_test: Entity is hidden: {is_entity_hidden(entity_obj.id)}")
editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", entity_obj.id, True)
general.idle_wait_frames(1)
general.log(f"{component_name}_test: Entity is shown: {not is_entity_hidden(entity_obj.id)}")
def verify_deletion_undo_redo(component_name, entity_obj):
editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntityById", entity_obj.id)
TestHelper.wait_for_condition(lambda: not hydra.find_entity_by_name(entity_obj.name), 2.0)
general.log(f"{component_name}_test: Entity deleted: {not hydra.find_entity_by_name(entity_obj.name)}")
general.undo()
TestHelper.wait_for_condition(lambda: hydra.find_entity_by_name(entity_obj.name) is not None, 2.0)
general.log(f"{component_name}_test: UNDO entity deletion works: "
f"{hydra.find_entity_by_name(entity_obj.name) is not None}")
general.redo()
TestHelper.wait_for_condition(lambda: not hydra.find_entity_by_name(entity_obj.name), 2.0)
general.log(f"{component_name}_test: REDO entity deletion works: "
f"{not hydra.find_entity_by_name(entity_obj.name)}")
def verify_required_component_addition(entity_obj, components_to_add, component_name):
def is_component_enabled(entity_componentid_pair):
return editor.EditorComponentAPIBus(bus.Broadcast, "IsComponentEnabled", entity_componentid_pair)
general.log(
f"{component_name}_test: Entity disabled initially: "
f"{not is_component_enabled(entity_obj.components[0])}")
for component in components_to_add:
entity_obj.add_component(component)
TestHelper.wait_for_condition(lambda: is_component_enabled(entity_obj.components[0]), 2.0)
general.log(
f"{component_name}_test: Entity enabled after adding "
f"required components: {is_component_enabled(entity_obj.components[0])}"
)
def verify_set_property(entity_obj, path, value):
entity_obj.get_set_test(0, path, value)
# Verify cubemap generation
def verify_cubemap_generation(component_name, entity_obj):
# Initially Check if the component has Reflection Probe component
if not hydra.has_components(entity_obj.id, ["Reflection Probe"]):
raise ValueError(f"Given entity {entity_obj.name} has no Reflection Probe component")
render.EditorReflectionProbeBus(azlmbr.bus.Event, "BakeReflectionProbe", entity_obj.id)
def get_value():
hydra.get_component_property_value(entity_obj.components[0], "Cubemap|Baked Cubemap Path")
TestHelper.wait_for_condition(lambda: get_value() != "", 20.0)
general.log(f"{component_name}_test: Cubemap is generated: {get_value() != ''}")
# Wait for Editor idle loop before executing Python hydra scripts.
TestHelper.init_idle()
# Delete all existing entities initially
search_filter = azlmbr.entity.SearchFilter()
all_entities = entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter)
editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntities", all_entities)
class ComponentTests:
"""Test launcher for each component."""
def __init__(self, component_name, *additional_tests):
self.component_name = component_name
self.additional_tests = additional_tests
self.run_component_tests()
def run_component_tests(self):
# Run common and additional tests
entity_obj = create_entity_undo_redo_component_addition(self.component_name)
# Enter/Exit game mode test
verify_enter_exit_game_mode(self.component_name)
# Any additional tests are executed here
for test in self.additional_tests:
test(entity_obj)
# Hide/Unhide entity test
verify_hide_unhide_entity(self.component_name, entity_obj)
# Deletion/Undo/Redo test
verify_deletion_undo_redo(self.component_name, entity_obj)
# DepthOfField Component
camera_entity = hydra.Entity("camera_entity")
camera_entity.create_entity(math.Vector3(512.0, 512.0, 34.0), ["Camera"])
depth_of_field = "DepthOfField"
ComponentTests(
depth_of_field,
lambda entity_obj: verify_required_component_addition(entity_obj, ["PostFX Layer"], depth_of_field),
lambda entity_obj: verify_set_property(
entity_obj, "Controller|Configuration|Camera Entity", camera_entity.id))
# Decal Component
material_asset_path = os.path.join("AutomatedTesting", "Materials", "basic_grey.material")
material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", material_asset_path, math.Uuid(), False)
ComponentTests(
"Decal", lambda entity_obj: verify_set_property(
entity_obj, "Controller|Configuration|Material", material_asset))
# Directional Light Component
ComponentTests(
"Directional Light",
lambda entity_obj: verify_set_property(
entity_obj, "Controller|Configuration|Shadow|Camera", camera_entity.id))
# Exposure Control Component
ComponentTests(
"Exposure Control", lambda entity_obj: verify_required_component_addition(
entity_obj, ["PostFX Layer"], "Exposure Control"))
# Global Skylight (IBL) Component
diffuse_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage")
diffuse_image_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", diffuse_image_path, math.Uuid(), False)
specular_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage")
specular_image_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", specular_image_path, math.Uuid(), False)
ComponentTests(
"Global Skylight (IBL)",
lambda entity_obj: verify_set_property(
entity_obj, "Controller|Configuration|Diffuse Image", diffuse_image_asset),
lambda entity_obj: verify_set_property(
entity_obj, "Controller|Configuration|Specular Image", specular_image_asset))
# Physical Sky Component
ComponentTests("Physical Sky")
# PostFX Layer Component
ComponentTests("PostFX Layer")
# PostFX Radius Weight Modifier Component
ComponentTests("PostFX Radius Weight Modifier")
# Light Component
ComponentTests("Light")
# Display Mapper Component
ComponentTests("Display Mapper")
# Reflection Probe Component
reflection_probe = "Reflection Probe"
ComponentTests(
reflection_probe,
lambda entity_obj: verify_required_component_addition(entity_obj, ["Box Shape"], reflection_probe),
lambda entity_obj: verify_cubemap_generation(reflection_probe, entity_obj),)
if __name__ == "__main__":
run()
@@ -0,0 +1,189 @@
"""
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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
bloom_creation = (
"Bloom Entity successfully created",
"Bloom Entity failed to be created")
bloom_component = (
"Entity has a Bloom component",
"Entity failed to find Bloom component")
bloom_disabled = (
"Bloom component disabled",
"Bloom component was not disabled")
postfx_layer_component = (
"Entity has a PostFX Layer component",
"Entity did not have an PostFX Layer component")
bloom_enabled = (
"Bloom component enabled",
"Bloom component was not enabled")
enable_bloom_parameter_enabled = (
"Enable Bloom parameter enabled",
"Enable Bloom parameter was not enabled")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
def AtomEditorComponents_Bloom_AddedToEntity():
"""
Summary:
Tests the Bloom component can be added to an entity and has the expected functionality.
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
Expected Behavior:
The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create an Bloom entity with no components.
2) Add Bloom component to Bloom entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Verify Bloom component not enabled.
6) Add PostFX Layer component since it is required by the Bloom component.
7) Verify Bloom component is enabled.
8) Enable the "Enable Bloom" parameter.
9) Enter/Exit game mode.
10) Test IsHidden.
11) Test IsVisible.
12) Delete Bloom entity.
13) UNDO deletion.
14) REDO deletion.
15) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Test steps begin.
# 1. Create an Bloom entity with no components.
bloom_entity = EditorEntity.create_editor_entity(AtomComponentProperties.bloom())
Report.critical_result(Tests.bloom_creation, bloom_entity.exists())
# 2. Add Bloom component to Bloom entity.
bloom_component = bloom_entity.add_component(AtomComponentProperties.bloom())
Report.critical_result(Tests.bloom_component, bloom_entity.has_component(AtomComponentProperties.bloom()))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not bloom_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, bloom_entity.exists())
# 5. Verify Bloom component not enabled.
Report.result(Tests.bloom_disabled, not bloom_component.is_enabled())
# 6. Add PostFX Layer component since it is required by the Bloom component.
bloom_entity.add_component(AtomComponentProperties.postfx_layer())
Report.result(
Tests.postfx_layer_component,
bloom_entity.has_component(AtomComponentProperties.postfx_layer()))
# 7. Verify Bloom component is enabled.
Report.result(Tests.bloom_enabled, bloom_component.is_enabled())
# 8. Enable the "Enable Bloom" parameter.
bloom_component.set_component_property_value(AtomComponentProperties.bloom('Enable Bloom'), True)
Report.result(
Tests.enable_bloom_parameter_enabled,
bloom_component.get_component_property_value(AtomComponentProperties.bloom('Enable Bloom')) is True)
# 9. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
# 10. Test IsHidden.
bloom_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, bloom_entity.is_hidden() is True)
# 11. Test IsVisible.
bloom_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, bloom_entity.is_visible() is True)
# 12. Delete Bloom entity.
bloom_entity.delete()
Report.result(Tests.entity_deleted, not bloom_entity.exists())
# 13. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, bloom_entity.exists())
# 14. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not bloom_entity.exists())
# 15. Look for errors and asserts.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_Bloom_AddedToEntity)
@@ -0,0 +1,193 @@
"""
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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
deferred_fog_creation = (
"Deferred Fog Entity successfully created",
"Deferred Fog Entity failed to be created")
deferred_fog_component = (
"Entity has a Deferred Fog component",
"Entity failed to find Deferred Fog component")
deferred_fog_disabled = (
"Deferred Fog component disabled",
"Deferred Fog component was not disabled")
postfx_layer_component = (
"Entity has a PostFX Layer component",
"Entity did not have an PostFX Layer component")
deferred_fog_enabled = (
"Deferred Fog component enabled",
"Deferred Fog component was not enabled")
enable_deferred_fog_parameter_enabled = (
"Enable Deferred Fog parameter enabled",
"Enable Deferred Fog parameter was not enabled")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
def AtomEditorComponents_DeferredFog_AddedToEntity():
"""
Summary:
Tests the Deferred Fog component can be added to an entity and has the expected functionality.
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
Expected Behavior:
The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create an Deferred Fog entity with no components.
2) Add Deferred Fog component to Deferred Fog entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Verify Deferred Fog component not enabled.
6) Add PostFX Layer component since it is required by the Deferred Fog component.
7) Verify Deferred Fog component is enabled.
8) Enable the "Enable Deferred Fog" parameter.
9) Enter/Exit game mode.
10) Test IsHidden.
11) Test IsVisible.
12) Delete Deferred Fog entity.
13) UNDO deletion.
14) REDO deletion.
15) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Test steps begin.
# 1. Create an Deferred Fog entity with no components.
deferred_fog_entity = EditorEntity.create_editor_entity(AtomComponentProperties.deferred_fog())
Report.critical_result(Tests.deferred_fog_creation, deferred_fog_entity.exists())
# 2. Add Deferred Fog component to Deferred Fog entity.
deferred_fog_component = deferred_fog_entity.add_component(
AtomComponentProperties.deferred_fog())
Report.critical_result(
Tests.deferred_fog_component,
deferred_fog_entity.has_component(AtomComponentProperties.deferred_fog()))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not deferred_fog_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, deferred_fog_entity.exists())
# 5. Verify Deferred Fog component not enabled.
Report.result(Tests.deferred_fog_disabled, not deferred_fog_component.is_enabled())
# 6. Add PostFX Layer component since it is required by the Deferred Fog component.
deferred_fog_entity.add_component(AtomComponentProperties.postfx_layer())
Report.result(
Tests.postfx_layer_component,
deferred_fog_entity.has_component(AtomComponentProperties.postfx_layer()))
# 7. Verify Deferred Fog component is enabled.
Report.result(Tests.deferred_fog_enabled, deferred_fog_component.is_enabled())
# 8. Enable the "Enable Deferred Fog" parameter.
deferred_fog_component.set_component_property_value(
AtomComponentProperties.deferred_fog('Enable Deferred Fog'), True)
Report.result(Tests.enable_deferred_fog_parameter_enabled,
deferred_fog_component.get_component_property_value(
AtomComponentProperties.deferred_fog('Enable Deferred Fog')) is True)
# 9. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
# 10. Test IsHidden.
deferred_fog_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, deferred_fog_entity.is_hidden() is True)
# 11. Test IsVisible.
deferred_fog_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, deferred_fog_entity.is_visible() is True)
# 12. Delete Deferred Fog entity.
deferred_fog_entity.delete()
Report.result(Tests.entity_deleted, not deferred_fog_entity.exists())
# 13. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, deferred_fog_entity.exists())
# 14. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not deferred_fog_entity.exists())
# 15. Look for errors and asserts.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_DeferredFog_AddedToEntity)
@@ -8,10 +8,10 @@
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_pytest(
NAME AutomatedTesting::MultiplayerTests_Main
TEST_SUITE main
NAME AutomatedTesting::MultiplayerTests_Sandbox
TEST_SUITE sandbox
TEST_SERIAL
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Sandbox.py
RUNTIME_DEPENDENCIES
Legacy::Editor
AZ::AssetProcessor
@@ -27,7 +27,3 @@ class TestAutomation(TestAutomationBase):
batch_mode=batch_mode,
autotest_mode=autotest_mode)
def test_Multiplayer_AutoComponent_NetworkInput(self, request, workspace, editor, launcher_platform):
from .tests import Multiplayer_AutoComponent_NetworkInput as test_module
self._run_prefab_test(request, workspace, editor, test_module)
@@ -0,0 +1,35 @@
"""
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
"""
# This suite consists of all test cases that are under development and have not been verified yet.
# Once they are verified, please move them to TestSuite_Active.py
import pytest
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared')
from base import TestAutomationBase
@pytest.mark.SUITE_sandbox
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestAutomation(TestAutomationBase):
def _run_prefab_test(self, request, workspace, editor, test_module, batch_mode=True, autotest_mode=True):
self._run_test(request, workspace, editor, test_module,
extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"],
batch_mode=batch_mode,
autotest_mode=autotest_mode)
## Seems to be flaky, need to investigate
def test_Multiplayer_AutoComponent_NetworkInput(self, request, workspace, editor, launcher_platform):
from .tests import Multiplayer_AutoComponent_NetworkInput as test_module
self._run_prefab_test(request, workspace, editor, test_module)
@@ -11,7 +11,6 @@ OutlinerWidget #m_display_options
{
qproperty-icon: url(:/Menu/menu.svg);
qproperty-iconSize: 16px 16px;
qproperty-flat: true;
}
OutlinerWidget QWidget[PulseHighlight="true"]
@@ -457,8 +457,6 @@ void JobManagerWorkStealing::ProcessJobsInternal(ThreadInfo* info, Job* suspende
else
{
//attempt to steal a job from another thread's queue
AZ_PROFILE_SCOPE(AzCore, "JobManagerWorkStealing::ProcessJobsInternal:WorkStealing");
unsigned int numStealAttempts = 0;
const unsigned int maxStealAttempts = (unsigned int)m_workerThreads.size() * 3; //try every thread a few times before giving up
while (!job)
@@ -10,6 +10,8 @@
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzFramework/Process/ProcessWatcher.h>
#include <sys/types.h>
#include <unistd.h>
@@ -24,14 +26,20 @@ namespace AzFramework::AssetSystem::Platform
AZ::IO::FixedMaxPath assetProcessorPath{ executableDirectory };
// In Mac the Editor and game is within a bundle, so the path to the sibling app
// has to go up from the Contents/MacOS folder the binary is in
assetProcessorPath /= "../../../AssetProcessor.app";
assetProcessorPath /= "../../../AssetProcessor.app/Contents/MacOS/AssetProcessor";
assetProcessorPath = assetProcessorPath.LexicallyNormal();
if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str()))
{
// Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure.
assetProcessorPath =
AZ::IO::FixedMaxPath{engineRoot} / "bin" / AZ_TRAIT_OS_PLATFORM_NAME / AZ_BUILD_CONFIGURATION_TYPE / "AssetProcessor.app";
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if (AZ::IO::FixedMaxPath installedBinariesPath;
settingsRegistry->Get(installedBinariesPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
{
// Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure.
assetProcessorPath = AZ::IO::FixedMaxPath{ engineRoot } / installedBinariesPath / "AssetProcessor.app/Contents/MacOS/AssetProcessor";
}
}
if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str()))
{
@@ -39,23 +47,21 @@ namespace AzFramework::AssetSystem::Platform
}
}
auto fullLaunchCommand = AZ::IO::FixedMaxPathString::format(R"(open -g "%s" --args --start-hidden)", assetProcessorPath.c_str());
AZStd::string commandLineParams;
// Add the engine path to the launch command if not empty
if (!engineRoot.empty())
{
fullLaunchCommand += R"( --engine-path=")";
fullLaunchCommand += engineRoot;
fullLaunchCommand += '"';
commandLineParams += AZStd::string::format("\"--engine-path=\"%s\"\"", engineRoot.data());
}
// Add the active project path to the launch command if not empty
if (!projectPath.empty())
{
fullLaunchCommand += R"( --project-path=")";
fullLaunchCommand += projectPath;
fullLaunchCommand += '"';
commandLineParams += AZStd::string::format(" \"--regset=/Amazon/AzCore/Bootstrap/project_path=\"%s\"\"", projectPath.data());
}
return system(fullLaunchCommand.c_str()) == 0;
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_processExecutableString = AZStd::move(assetProcessorPath.Native());
processLaunchInfo.m_commandlineParameters = commandLineParams;
return AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
}
}
@@ -13,7 +13,9 @@
<Member Type="AzNetworking::DisconnectReason" Name="disconnectReason" Init="AzNetworking::DisconnectReason::None" />
</Packet>
<Packet Name="HeartbeatPacket" Desc="This packet is used to keep an established connection alive" />
<Packet Name="HeartbeatPacket" Desc="This packet is used to keep an established connection alive">
<Member Type="bool" Name="requestResponse" Init="false" />
</Packet>
<Packet Name="FragmentedPacket" Desc="This packet is used to segment a packet that exceeds a connections MTU">
<Member Type="AzNetworking::SequenceId" Name="unfragmentedSequence" Init="AzNetworking::InvalidSequenceId" />
@@ -122,10 +122,4 @@ namespace AzNetworking
m_timeoutItemMap.erase(itemTimeoutId);
}
}
void TimeoutQueue::UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts)
{
TimeoutHandler handler([&timeoutHandler](TimeoutQueue::TimeoutItem& item) { return timeoutHandler.HandleTimeout(item); });
UpdateTimeouts(handler, maxTimeouts);
}
}
@@ -23,8 +23,6 @@ namespace AzNetworking
Delete
};
class ITimeoutHandler;
//! @class TimeoutQueue
//! @brief class for managing timeout items.
class TimeoutQueue
@@ -70,11 +68,6 @@ namespace AzNetworking
using TimeoutHandler = AZStd::function<TimeoutResult(TimeoutQueue::TimeoutItem&)>;
void UpdateTimeouts(const TimeoutHandler& timeoutHandler, int32_t maxTimeouts = -1);
//! Updates timeouts for all items, invokes timeout handlers if required.
//! @param timeoutHandler listener instance to call back on for timeouts
//! @param maxTimeouts the maximum number of timeouts to process before breaking iteration
void UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts = -1);
private:
struct TimeoutQueueItem
@@ -94,19 +87,6 @@ namespace AzNetworking
TimeoutItemMap m_timeoutItemMap;
TimeoutItemQueue m_timeoutItemQueue;
};
//! @class ITimeoutHandler
//! @brief interface class for managing timeout items.
class ITimeoutHandler
{
public:
virtual ~ITimeoutHandler() = default;
//! Handler callback for timed out items.
//! @param item containing registered timeout details
//! @return ETimeoutResult for whether to re-register or discard the timeout params
virtual TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) = 0;
};
}
#include <AzNetworking/DataStructures/TimeoutQueue.inl>
@@ -27,13 +27,11 @@ namespace AzNetworking
ConnectionId connectionId,
const IpAddress& remoteAddress,
TcpNetworkInterface& networkInterface,
TcpSocket& socket,
TimeoutId timeoutId
TcpSocket& socket
)
: IConnection(connectionId, remoteAddress)
, m_networkInterface(networkInterface)
, m_socket(socket.CloneAndTakeOwnership())
, m_timeoutId(timeoutId)
, m_state(m_socket->IsOpen() ? ConnectionState::Connecting : ConnectionState::Disconnected)
, m_connectionRole(ConnectionRole::Acceptor)
, m_registeredSocketFd(InvalidSocketFd)
@@ -163,13 +161,6 @@ namespace AzNetworking
break;
}
TimeoutQueue::TimeoutItem* timeoutItem = m_networkInterface.m_connectionTimeoutQueue.RetrieveItem(GetTimeoutId());
if (timeoutItem == nullptr)
{
return true;
}
timeoutItem->UpdateTimeoutTime(startTimeMs);
NetworkOutputSerializer serializer(buffer.GetBuffer(), static_cast<uint32_t>(buffer.GetSize()));
if (m_state == ConnectionState::Connecting)
{
@@ -38,14 +38,12 @@ namespace AzNetworking
//! @param remoteAddress IP address of the remote endpoint
//! @param networkInterface TcpNetworkInterface that owns this connection instance
//! @param socket TCP socket to take ownership of and use for sending and receiving data
//! @param timeoutId timeout identifier of this connection instance
TcpConnection
(
ConnectionId connectionId,
const IpAddress& remoteAddress,
TcpNetworkInterface& networkInterface,
TcpSocket& socket,
TimeoutId timeoutId
TcpSocket& socket
);
//! Construct a new socket with optional encryption, used when initiating a new connection
@@ -69,14 +67,6 @@ namespace AzNetworking
//! @return the TcpSocket bound to this TcpConnection
TcpSocket* GetTcpSocket() const;
//! Sets the timeout identifier for this TcpConnection.
//! @param timeoutId the timeout identifier to use for this TcpConnection
void SetTimeoutId(TimeoutId timeoutId);
//! Returns the timeout identifier for this TcpConnection.
//! @return the timeout identifier for this TcpConnection
TimeoutId GetTimeoutId() const;
//! Returns true if this connection instance is in an open state, and is capable of actively sending and receiving packets.
//! @return boolean true if this connection instance is in an open state
bool IsOpen() const;
@@ -142,7 +132,6 @@ namespace AzNetworking
AZStd::unique_ptr<TcpSocket> m_socket;
AZStd::unique_ptr<ICompressor> m_compressor;
TimeoutId m_timeoutId;
PacketId m_lastSentPacketId = InvalidPacketId;
ConnectionState m_state = ConnectionState::Disconnected;
ConnectionRole m_connectionRole = ConnectionRole::Connector;
@@ -15,16 +15,6 @@ namespace AzNetworking
return m_socket.get();
}
inline void TcpConnection::SetTimeoutId(TimeoutId timeoutId)
{
m_timeoutId = timeoutId;
}
inline TimeoutId TcpConnection::GetTimeoutId() const
{
return m_timeoutId;
}
inline bool TcpConnection::IsOpen() const
{
return m_socket->IsOpen();
@@ -21,16 +21,11 @@ namespace AzNetworking
static const bool net_TcpUseEncryption = false;
#endif
AZ_CVAR(bool, net_TcpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Tcp connections");
AZ_CVAR(AZ::TimeMs, net_TcpHeartbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Tcp connection heartbeat frequency");
AZ_CVAR(AZ::TimeMs, net_TcpDefaultTimeoutMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Tcp connection");
TcpNetworkInterface::TcpNetworkInterface(AZ::Name name, IConnectionListener& connectionListener, TrustZone trustZone, TcpListenThread& listenThread)
: m_name(name)
, m_trustZone(trustZone)
, m_connectionListener(connectionListener)
, m_listenThread(listenThread)
, m_timeoutMs(net_TcpDefaultTimeoutMs)
{
;
}
@@ -98,8 +93,6 @@ namespace AzNetworking
}
AZLOG_INFO("Adding new socket %d", static_cast<int32_t>(tcpSocket->GetSocketFd()));
const TimeoutId newTimeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast<uint64_t>(tcpSocket->GetSocketFd()), net_TcpHeartbeatTimeMs);
connection->SetTimeoutId(newTimeoutId);
connection->SendReliablePacket(CorePackets::InitiateConnectionPacket());
m_connectionListener.OnConnect(connection.get());
m_connectionSet.AddConnection(AZStd::move(connection));
@@ -110,12 +103,6 @@ namespace AzNetworking
{
const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs();
// Time out any stale connections
{
ConnectionTimeoutFunctor functor(*this);
m_connectionTimeoutQueue.UpdateTimeouts(functor);
}
AcceptNewConnections();
auto readCallback = [this, startTimeMs](SocketFd socketFd) { HandleConnectionRecv(socketFd, startTimeMs); };
@@ -258,8 +245,7 @@ namespace AzNetworking
return;
}
AZLOG(NET_TcpTraffic, "Adding new socket %d", static_cast<int32_t>(tcpSocket.GetSocketFd()));
const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast<uint64_t>(tcpSocket.GetSocketFd()), m_timeoutMs);
AZStd::unique_ptr<TcpConnection> connection = AZStd::make_unique<TcpConnection>(connectionId, remoteAddress, *this, tcpSocket, timeoutId);
AZStd::unique_ptr<TcpConnection> connection = AZStd::make_unique<TcpConnection>(connectionId, remoteAddress, *this, tcpSocket);
AZ_Assert(connection->GetConnectionRole() == ConnectionRole::Acceptor, "Invalid role for connection");
GetConnectionListener().OnConnect(connection.get());
m_connectionSet.AddConnection(AZStd::move(connection));
@@ -286,7 +272,6 @@ namespace AzNetworking
m_pendingRemoves.resize_no_construct(0);
}
TcpNetworkInterface::PendingConnection::PendingConnection(SocketFd socketFd, uint32_t remoteIpAddress, uint16_t remotePort, uint16_t listenPort)
: m_socketFd(socketFd)
, m_remoteIpAddress(remoteIpAddress)
@@ -295,34 +280,4 @@ namespace AzNetworking
{
;
}
TcpNetworkInterface::ConnectionTimeoutFunctor::ConnectionTimeoutFunctor(TcpNetworkInterface& networkInterface)
: m_networkInterface(networkInterface)
{
;
}
TimeoutResult TcpNetworkInterface::ConnectionTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item)
{
const SocketFd socketFd = static_cast<SocketFd>(item.m_userData);
TcpConnection* tcpConnection = m_networkInterface.m_connectionSet.GetConnection(socketFd);
if (tcpConnection == nullptr)
{
// We've already deleted this connection
return TimeoutResult::Delete;
}
if (tcpConnection->GetConnectionRole() == ConnectionRole::Connector)
{
tcpConnection->SendReliablePacket(CorePackets::HeartbeatPacket());
}
else if (net_TcpTimeoutConnections && (m_networkInterface.GetTimeoutMs() > AZ::TimeMs{ 0 }))
{
tcpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local);
return TimeoutResult::Delete;
}
return TimeoutResult::Refresh;
}
}
@@ -137,16 +137,6 @@ namespace AzNetworking
AZ_DISABLE_COPY_MOVE(TcpNetworkInterface);
struct ConnectionTimeoutFunctor final
: public ITimeoutHandler
{
ConnectionTimeoutFunctor(TcpNetworkInterface& networkInterface);
TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override;
private:
AZ_DISABLE_COPY_MOVE(ConnectionTimeoutFunctor);
TcpNetworkInterface& m_networkInterface;
};
struct PendingRemove
{
SocketFd m_socketFd;
@@ -162,7 +152,6 @@ namespace AzNetworking
TcpSocketManager m_tcpSocketManager;
AZ::ThreadSafeDeque<PendingConnection> m_pendingConnections;
AZStd::vector<PendingRemove> m_pendingRemoves;
TimeoutQueue m_connectionTimeoutQueue;
TcpListenThread& m_listenThread;
friend class TcpConnection; // For access to private RequestDisconnect() method
@@ -79,7 +79,8 @@ namespace AzNetworking
AZLOG(NET_Acks, "Unacked packet count exceeded, sending client heartbeat (curr %u : max %u)", m_unackedPacketCount, static_cast<uint32_t>(net_UdpMaxUnackedPacketCount));
// This simply times out unreliable chunks that haven't completed within our timeout delay
m_fragmentQueue.Update();
SendUnreliablePacket(CorePackets::HeartbeatPacket());
// This heartbeat is sent to minimize the time the remote endpoint spends waiting for ack vector replication, we don't require a response
SendUnreliablePacket(CorePackets::HeartbeatPacket(false));
}
}
@@ -289,7 +290,11 @@ namespace AzNetworking
{
return PacketDispatchResult::Failure;
}
// Do nothing, we've already processed our ack packets
if (packet.GetRequestResponse())
{
// We're replying to a heartbeat request, we don't want a response
SendUnreliablePacket(CorePackets::HeartbeatPacket(false));
}
return PacketDispatchResult::Success;
}
break;
@@ -136,12 +136,12 @@ namespace AzNetworking
AZ_DISABLE_COPY_MOVE(UdpConnection);
UdpNetworkInterface& m_networkInterface;
UdpPacketTracker m_packetTracker;
UdpReliableQueue m_reliableQueue;
UdpFragmentQueue m_fragmentQueue;
ConnectionState m_state = ConnectionState::Disconnected;
ConnectionRole m_connectionRole = ConnectionRole::Connector;
DtlsEndpoint m_dtlsEndpoint;
UdpPacketTracker m_packetTracker;
UdpReliableQueue m_reliableQueue;
UdpFragmentQueue m_fragmentQueue;
ConnectionState m_state = ConnectionState::Disconnected;
ConnectionRole m_connectionRole = ConnectionRole::Connector;
DtlsEndpoint m_dtlsEndpoint;
AZ::TimeMs m_lastSentPacketMs;
uint32_t m_unackedPacketCount = 0;
@@ -20,7 +20,13 @@ namespace AzNetworking
void UdpFragmentQueue::Update()
{
m_timeoutQueue.UpdateTimeouts(*this);
m_timeoutQueue.UpdateTimeouts([this](TimeoutQueue::TimeoutItem& item)
{
const SequenceId fragmentSequence = static_cast<SequenceId>(item.m_userData & 0xFF);
AZLOG(NET_FragmentQueue, "Timing out unreliable fragmented packet %u", static_cast<uint32_t>(fragmentSequence));
m_packetFragments.erase(fragmentSequence);
return TimeoutResult::Delete;
});
}
void UdpFragmentQueue::Reset()
@@ -163,12 +169,4 @@ namespace AzNetworking
return handledPacket;
}
TimeoutResult UdpFragmentQueue::HandleTimeout(TimeoutQueue::TimeoutItem& item)
{
const SequenceId fragmentSequence = static_cast<SequenceId>(item.m_userData & 0xFF);
AZLOG(NET_FragmentQueue, "Timing out unreliable fragmented packet %u", static_cast<uint32_t>(fragmentSequence));
m_packetFragments.erase(fragmentSequence);
return TimeoutResult::Delete;
}
}
@@ -26,7 +26,6 @@ namespace AzNetworking
//! @class UdpFragmentQueue
//! @brief Class for reconstructing packet chunks into the original unsegmented packet.
class UdpFragmentQueue
: public ITimeoutHandler
{
public:
@@ -51,11 +50,6 @@ namespace AzNetworking
private:
//! Handler callback for timed out items.
//! @param item containing registered timeout details
//! @return ETimeoutResult for whether to re-register or discard the timeout params
TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override;
TimeoutQueue m_timeoutQueue;
SequenceGenerator m_sequenceGenerator;
@@ -31,7 +31,7 @@ namespace AzNetworking
AZ_CVAR(bool, net_UdpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Udp connections");
AZ_CVAR(AZ::TimeMs, net_UdpPacketTimeSliceMs, AZ::TimeMs{ 8 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The number of milliseconds to allow for packet processing");
AZ_CVAR(AZ::TimeMs, net_UdpHeartbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Udp connection heartbeat frequency");
AZ_CVAR(uint32_t, net_UdpUnackedHeartbeats, 5, nullptr, AZ::ConsoleFunctorFlags::Null, "The number of heartbeats to attempt to send to keep a connection alive before giving up");
AZ_CVAR(AZ::TimeMs, net_UdpDefaultTimeoutMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Udp connection");
AZ_CVAR(AZ::TimeMs, net_MinPacketTimeoutMs, AZ::TimeMs{ 200 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Minimum time to wait before timing out an unacked packet");
AZ_CVAR(int32_t, net_MaxTimeoutsPerFrame, 1000, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Maximum number of packet timeouts to allow to process in a single frame");
@@ -139,7 +139,8 @@ namespace AzNetworking
}
const ConnectionId connectionId = m_connectionSet.GetNextConnectionId();
const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast<uint64_t>(connectionId), m_timeoutMs);
const AZ::TimeMs timeoutTimeMs = m_timeoutMs / static_cast<AZ::TimeMs>(static_cast<int32_t>(net_UdpUnackedHeartbeats));
const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast<uint64_t>(connectionId), timeoutTimeMs);
AZStd::unique_ptr<UdpConnection> connection = AZStd::make_unique<UdpConnection>(connectionId, remoteAddress, *this, ConnectionRole::Connector);
UdpPacketEncodingBuffer dtlsData;
@@ -277,6 +278,7 @@ namespace AzNetworking
}
timeoutItem->UpdateTimeoutTime(startTimeMs);
connection->m_timeoutCounter = 0;
PacketDispatchResult handledPacket = PacketDispatchResult::Failure;
if (header.GetPacketType() < aznumeric_cast<PacketType>(CorePackets::PacketType::MAX))
@@ -319,16 +321,10 @@ namespace AzNetworking
const AZ::TimeMs receiveTimeMs = AZ::GetElapsedTimeMs() - startTimeMs;
// Time out any stale client connections
{
ConnectionTimeoutFunctor functor(*this);
m_connectionTimeoutQueue.UpdateTimeouts(functor);
}
m_connectionTimeoutQueue.UpdateTimeouts([this](TimeoutQueue::TimeoutItem& item) { return HandleConnectionTimeout(item); });
// Time out any packets that haven't been acked within our timeout window
{
PacketTimeoutFunctor functor(*this);
m_packetTimeoutQueue.UpdateTimeouts(functor, static_cast<int32_t>(net_MaxTimeoutsPerFrame));
}
m_packetTimeoutQueue.UpdateTimeouts([this](TimeoutQueue::TimeoutItem& item) { return HandlePacketTimeout(item); }, static_cast<int32_t>(net_MaxTimeoutsPerFrame));
// Delete any connections we've disconnected
for (RemovedConnection& removedConnection : m_removedConnections)
@@ -709,21 +705,14 @@ namespace AzNetworking
{
// Packets involved in handshake are InitiateConnection, ConnectionHandshake and FragmentedPackets of ConnectionHandshake
return packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::InitiateConnectionPacket) ||
packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::ConnectionHandshakePacket) ||
(packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::FragmentedPacket) && endpoint.IsConnecting());
packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::ConnectionHandshakePacket) ||
(packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::FragmentedPacket) && endpoint.IsConnecting());
}
UdpNetworkInterface::ConnectionTimeoutFunctor::ConnectionTimeoutFunctor(UdpNetworkInterface& networkInterface)
: m_networkInterface(networkInterface)
{
;
}
TimeoutResult UdpNetworkInterface::ConnectionTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item)
TimeoutResult UdpNetworkInterface::HandleConnectionTimeout(TimeoutQueue::TimeoutItem& item)
{
const ConnectionId connectionId = ConnectionId(aznumeric_cast<uint32_t>(item.m_userData));
UdpConnection* udpConnection = static_cast<UdpConnection*>(m_networkInterface.m_connectionSet.GetConnection(connectionId));
UdpConnection* udpConnection = static_cast<UdpConnection*>(m_connectionSet.GetConnection(connectionId));
if (udpConnection == nullptr)
{
@@ -731,22 +720,23 @@ namespace AzNetworking
return TimeoutResult::Delete;
}
if (udpConnection->GetConnectionState() == ConnectionState::Connecting)
if ((udpConnection->GetConnectionState() == ConnectionState::Connecting)
&& udpConnection->GetDtlsEndpoint().IsConnecting())
{
if (udpConnection->GetDtlsEndpoint().IsConnecting())
{
// DTLS prefers we resend data lost over the wire with fresh SSL IDs so account for that here
UdpPacketEncodingBuffer dtlsData;
udpConnection->ProcessHandshakeData(dtlsData);
return TimeoutResult::Refresh;
}
// DTLS prefers we resend data lost over the wire with fresh SSL IDs so account for that here
UdpPacketEncodingBuffer dtlsData;
udpConnection->ProcessHandshakeData(dtlsData);
return TimeoutResult::Refresh;
}
if (udpConnection->GetConnectionRole() == ConnectionRole::Connector)
if ((udpConnection->GetConnectionRole() == ConnectionRole::Connector)
&& (udpConnection->m_timeoutCounter < net_UdpUnackedHeartbeats))
{
udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket());
// Set the request response flag to true since we want a response to keep the connection alive
udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket(true));
++udpConnection->m_timeoutCounter;
}
else if (net_UdpTimeoutConnections && (m_networkInterface.GetTimeoutMs() > AZ::TimeMs{ 0 }))
else if (net_UdpTimeoutConnections && (GetTimeoutMs() > AZ::TimeMs{ 0 }))
{
udpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local);
return TimeoutResult::Delete;
@@ -755,19 +745,13 @@ namespace AzNetworking
return TimeoutResult::Refresh;
}
UdpNetworkInterface::PacketTimeoutFunctor::PacketTimeoutFunctor(UdpNetworkInterface& networkInterface)
: m_networkInterface(networkInterface)
{
;
}
TimeoutResult UdpNetworkInterface::PacketTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item)
TimeoutResult UdpNetworkInterface::HandlePacketTimeout(TimeoutQueue::TimeoutItem& item)
{
ConnectionId connectionId;
PacketId packetId;
ReliabilityType reliability;
DecodeTimeoutId(item.m_userData, connectionId, packetId, reliability);
UdpConnection* connection = static_cast<UdpConnection*>(m_networkInterface.m_connectionSet.GetConnection(connectionId));
UdpConnection* connection = static_cast<UdpConnection*>(m_connectionSet.GetConnection(connectionId));
if (connection == nullptr)
{
@@ -782,16 +766,14 @@ namespace AzNetworking
case PacketTimeoutResult::Acked:
// Packet was already acked, just discard this timeout entry
return TimeoutResult::Delete;
case PacketTimeoutResult::Pending:
// Packet timed out before we received any info about it's sequence from the remote endpoint
// The connection latency may have increased, and our Rtt metrics may still be adjusting..
// Just throw it back into the timeout queue
return TimeoutResult::Refresh;
case PacketTimeoutResult::Lost:
// Packet timed out and was not acked, so we consider it lost
m_networkInterface.m_connectionListener.OnPacketLost(connection, packetId);
m_connectionListener.OnPacketLost(connection, packetId);
break;
}
@@ -149,34 +149,24 @@ namespace AzNetworking
//! @param endpoint whether the disconnection was initiated locally or remotely
void RequestDisconnect(UdpConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint);
//! Internal helper to check if a packet's type is for connection handshake
//! Internal helper to check if a packet's type is for connection handshake.
//! @param endpoint DTLS endpoint participating in the handshake
//! @param packetType type of the packet
//! @return if the packet is for handshake
bool IsHandshakePacket(const DtlsEndpoint& endpoint, AzNetworking::PacketType packetType) const;
//! Internal helper to manage connection timeout behaviour.
//! @param item the timeout item corresponding to the timed out connection
//! @return whether to delete or persist the timeout item
TimeoutResult HandleConnectionTimeout(TimeoutQueue::TimeoutItem& item);
//! Internal helper to manage packet timeout behaviour.
//! @param item the timeout item corresponding to the timed out packet
//! @return whether to delete or persist the timeout item
TimeoutResult HandlePacketTimeout(TimeoutQueue::TimeoutItem& item);
AZ_DISABLE_COPY_MOVE(UdpNetworkInterface);
struct ConnectionTimeoutFunctor final
: public ITimeoutHandler
{
ConnectionTimeoutFunctor(UdpNetworkInterface& networkInterface);
TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override;
private:
AZ_DISABLE_COPY_MOVE(ConnectionTimeoutFunctor);
UdpNetworkInterface& m_networkInterface;
};
struct PacketTimeoutFunctor final
: public ITimeoutHandler
{
PacketTimeoutFunctor(UdpNetworkInterface& networkInterface);
TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override;
private:
AZ_DISABLE_COPY_MOVE(PacketTimeoutFunctor);
UdpNetworkInterface& m_networkInterface;
};
AZ::Name m_name;
TrustZone m_trustZone;
uint16_t m_port = 0;
@@ -10,7 +10,6 @@ AzToolsFramework--EntityOutlinerWidget #m_display_options
{
qproperty-icon: url(:/stylesheet/img/UI20/menu-centered.svg);
qproperty-iconSize: 16px 16px;
qproperty-flat: true;
}
AzToolsFramework--EntityOutlinerWidget QTreeView
-337
View File
@@ -96,47 +96,6 @@ unsigned countElements (const std::vector<T>& arrT, const T& x)
*/
namespace stl
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Compare member of class/struct.
//
// e.g. Sort Vec3s by x component
//
// std::sort(vec3s.begin(), vec3s.end(), stl::member_compare<Vec3, float, &Vec3::x>());
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename OWNER_TYPE, typename MEMBER_TYPE, MEMBER_TYPE OWNER_TYPE::* MEMBER_PTR, typename EQUALITY = std::less<MEMBER_TYPE> >
struct member_compare
{
inline bool operator () (const OWNER_TYPE& lhs, const OWNER_TYPE& rhs) const
{
return EQUALITY()(lhs.*MEMBER_PTR, rhs.*MEMBER_PTR);
}
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Compare member of class/struct against parameter.
//
// e.g. Find Vec3 with x component less than 1.0
//
// std::find_if(vec3s.begin(), vec3s.end(), stl::member_compare_param<Vec3, float, &Vec3::x>(1.0f));
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename OWNER_TYPE, typename MEMBER_TYPE, MEMBER_TYPE OWNER_TYPE::* MEMBER_PTR, typename EQUALITY = std::less<MEMBER_TYPE> >
struct member_compare_param
{
inline member_compare_param(const MEMBER_TYPE& _value)
: value(_value)
{
}
inline bool operator () (const OWNER_TYPE& rhs) const
{
return EQUALITY()(rhs.*MEMBER_PTR, value);
}
const MEMBER_TYPE& value;
};
//////////////////////////////////////////////////////////////////////////
//! Searches the given entry in the map by key, and if there is none, returns the default value
//////////////////////////////////////////////////////////////////////////
@@ -154,48 +113,6 @@ namespace stl
}
}
//////////////////////////////////////////////////////////////////////////
//! Inserts and returns a reference to the given value in the map, or returns the current one if it's already there.
//////////////////////////////////////////////////////////////////////////
template <typename Map>
inline typename Map::mapped_type& map_insert_or_get(Map& mapKeyToValue, const typename Map::key_type& key, const typename Map::mapped_type& defValue = typename Map::mapped_type())
{
auto&& iresult = mapKeyToValue.insert(typename Map::value_type(key, defValue));
return iresult.first->second;
}
// searches the given entry in the map by key, and if there is none, returns the default value
// The values are taken/returned in REFERENCEs rather than values
template <typename Key, typename mapped_type, typename Traits, typename Allocator>
inline mapped_type& find_in_map_ref(std::map<Key, mapped_type, Traits, Allocator>& mapKeyToValue, const Key& key, mapped_type& valueDefault)
{
typedef std::map<Key, mapped_type, Traits, Allocator> Map;
typename Map::iterator it = mapKeyToValue.find (key);
if (it == mapKeyToValue.end())
{
return valueDefault;
}
else
{
return it->second;
}
}
template <typename Key, typename mapped_type, typename Traits, typename Allocator>
inline const mapped_type& find_in_map_ref(const std::map<Key, mapped_type, Traits, Allocator>& mapKeyToValue, const Key& key, const mapped_type& valueDefault)
{
typedef std::map<Key, mapped_type, Traits, Allocator> Map;
typename Map::const_iterator it = mapKeyToValue.find (key);
if (it == mapKeyToValue.end())
{
return valueDefault;
}
else
{
return it->second;
}
}
//////////////////////////////////////////////////////////////////////////
//! Fills vector with contents of map.
//////////////////////////////////////////////////////////////////////////
@@ -210,20 +127,6 @@ namespace stl
}
}
//////////////////////////////////////////////////////////////////////////
//! Fills vector with contents of set.
//////////////////////////////////////////////////////////////////////////
template <class Set, class Vector>
inline void set_to_vector(const Set& theSet, Vector& array)
{
array.resize(0);
array.reserve(theSet.size());
for (typename Set::const_iterator it = theSet.begin(); it != theSet.end(); ++it)
{
array.push_back(*it);
}
}
//////////////////////////////////////////////////////////////////////////
//! Find and erase element from container.
// @return true if item was find and erased, false if item not found.
@@ -312,48 +215,6 @@ namespace stl
return false;
}
//////////////////////////////////////////////////////////////////////////
//! Push back to container unique element.
// @return true if item added, false overwise.
template <class CONTAINER, class PREDICATE, typename VALUE>
inline bool push_back_unique_if(CONTAINER& container, const PREDICATE& predicate, const VALUE& value)
{
typename CONTAINER::iterator end = container.end();
if (AZStd::find_if(container.begin(), end, predicate) == end)
{
container.push_back(value);
return true;
}
else
{
return false;
}
}
//////////////////////////////////////////////////////////////////////////
//! Push back to container contents of another container
template <class Container, class Iter>
inline void push_back_range(Container& container, Iter begin, Iter end)
{
for (Iter it = begin; it != end; ++it)
{
container.push_back(*it);
}
}
//////////////////////////////////////////////////////////////////////////
//! Push back to container contents of another container, if not already present
template <class Container, class Iter>
inline void push_back_range_unique(Container& container, Iter begin, Iter end)
{
for (Iter it = begin; it != end; ++it)
{
push_back_unique(container, *it);
}
}
//////////////////////////////////////////////////////////////////////////
//! Find element in container.
// @return true if item found.
@@ -373,107 +234,6 @@ namespace stl
return (it == last || value != *it) ? last : it;
}
//////////////////////////////////////////////////////////////////////////
//! Find element in a sorted container using binary search with logarithmic efficiency.
// @return true if item was inserted.
template <class Container, class Value>
inline bool binary_insert_unique(Container& container, const Value& value)
{
typename Container::iterator it = std::lower_bound(container.begin(), container.end(), value);
if (it != container.end())
{
if (*it == value)
{
return false;
}
container.insert(it, value);
}
else
{
container.insert(container.end(), value);
}
return true;
}
//////////////////////////////////////////////////////////////////////////
//! Find element in a sorted container using binary search with logarithmic efficiency.
// and erases if element found.
// @return true if item was erased.
template <class Container, class Value>
inline bool binary_erase(Container& container, const Value& value)
{
typename Container::iterator it = std::lower_bound(container.begin(), container.end(), value);
if (it != container.end() && *it == value)
{
container.erase(it);
return true;
}
return false;
}
template <typename ItT, typename Func>
ItT remove_from_heap(ItT begin, ItT end, ItT at, Func order)
{
using std::swap;
--end;
if (at == end)
{
return at;
}
size_t idx = std::distance(begin, at);
swap(*end, *at);
size_t length = std::distance(begin, end);
size_t parent, child;
if (idx > 0 && order(*(begin + idx / 2), *(begin + idx)))
{
do
{
parent = idx / 2;
swap(*(begin + idx), *(begin + parent));
idx = parent;
if (idx == 0 || order(*(begin + idx), *(begin + idx / 2)))
{
return end;
}
}
while (true);
}
else
{
do
{
child = idx * 2 + 1;
if (child >= length)
{
return end;
}
ItT left = begin + child;
ItT right = begin + child + 1;
if (right < end && order(*left, *right))
{
++child;
}
if (order(*(begin + child), *(begin + idx)))
{
return end;
}
swap(*(begin + child), *(begin + idx));
idx = child;
}
while (true);
}
return end;
}
struct container_object_deleter
{
template<typename T>
@@ -506,18 +266,6 @@ namespace stl
return type.c_str();
}
//////////////////////////////////////////////////////////////////////////
//! Case sensetive less key for any type convertable to const char*.
//////////////////////////////////////////////////////////////////////////
template <class Type>
struct less_strcmp
{
bool operator()(const Type& left, const Type& right) const
{
return strcmp(constchar_cast(left), constchar_cast(right)) < 0;
}
};
//////////////////////////////////////////////////////////////////////////
//! Case insensetive less key for any type convertable to const char*.
template <class Type>
@@ -690,89 +438,4 @@ namespace stl
stl::free_container(container);
}
};
template <typename T, size_t Length, typename Func>
inline void for_each_array(T (&buffer)[Length], Func func)
{
std::for_each(&buffer[0], &buffer[Length], func);
}
template <typename T, typename D, size_t Length, typename Func>
inline void for_each_array(StaticInstance<T, D>(&buffer)[Length], Func func)
{
for (size_t idx = 0; idx < Length; ++idx)
{
func(*buffer[idx]);
}
}
template <typename T>
inline void destruct(T* p)
{
p->~T();
}
}
#define DEFINE_INTRUSIVE_LINKED_LIST(Class) \
template<> \
Class * stl::intrusive_linked_list_node<Class>::m_root_intrusive = nullptr;
// define the maplikestruct, used to approximate the memory requirements for a map node
namespace stl
{
struct MapLikeStruct
{
bool color;
void* parent;
void* left;
void* right;
};
}
template <class Map>
unsigned sizeOfMap(Map& map)
{
unsigned size = 0;
for (typename Map::iterator it = map.begin(); it != map.end(); it++)
{
typename Map::mapped_type& T = it->second;
size += T.Size();
}
size += map.size() * sizeof(stl::MapLikeStruct);
return size;
}
template <class Map>
unsigned sizeOfMapStr(Map& map)
{
unsigned size = 0;
for (typename Map::iterator it = map.begin(); it != map.end(); it++)
{
typename Map::mapped_type& T = it->second;
size += T.capacity();
}
size += map.size() * sizeof(stl::MapLikeStruct);
return size;
}
template <class Map>
unsigned sizeOfMapP(Map& map)
{
unsigned size = 0;
for (typename Map::iterator it = map.begin(); it != map.end(); it++)
{
typename Map::mapped_type& T = it->second;
size += T->Size();
}
size += map.size() * sizeof(stl::MapLikeStruct);
return size;
}
template <class Map>
unsigned sizeOfMapS(Map& map)
{
unsigned size = 0;
for (typename Map::iterator it = map.begin(); it != map.end(); it++)
{
typename Map::mapped_type& T = it->second;
size += sizeof(T);
}
size += map.size() * sizeof(stl::MapLikeStruct);
return size;
}
-2
View File
@@ -509,8 +509,6 @@ void CSystem::ShutDown()
ShutdownFileSystem();
ShutdownModuleLibraries();
EBUS_EVENT(CrySystemEventBus, OnCrySystemPostShutdown);
}
-12
View File
@@ -303,8 +303,6 @@ public:
void SetVersionInfo(const char* const szVersion);
#endif
void ShutdownModuleLibraries();
#if defined(WIN32)
friend LRESULT WINAPI WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
#endif
@@ -323,8 +321,6 @@ private:
// Release all resources.
void ShutDown();
bool LoadEngineDLLs();
//! @name Initialization routines
//@{
bool InitConsole();
@@ -340,11 +336,8 @@ private:
void CreateSystemVars();
void CreateAudioVars();
AZStd::unique_ptr<AZ::DynamicModuleHandle> LoadDLL(const char* dllName);
void FreeLib(AZStd::unique_ptr<AZ::DynamicModuleHandle>& hLibModule);
bool UnloadDLL(const char* dllName);
void QueryVersionInfo();
void LogVersion();
void LogBuildInfo();
@@ -359,8 +352,6 @@ private:
void AddCVarGroupDirectory(const AZStd::string& sPath) override;
AZStd::unique_ptr<AZ::DynamicModuleHandle> LoadDynamiclibrary(const char* dllName) const;
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION SYSTEM_H_SECTION_3
#include AZ_RESTRICTED_FILE(System_h)
@@ -416,9 +407,6 @@ private: // ------------------------------------------------------
bool m_bDrawConsole; //!< Set to true if OK to draw the console.
bool m_bDrawUI; //!< Set to true if OK to draw UI.
std::map<AZ::Crc32, AZStd::unique_ptr<AZ::DynamicModuleHandle> > m_moduleDLLHandles;
//! current active process
IProcess* m_pProcess;
-207
View File
@@ -12,7 +12,6 @@
#if defined(AZ_RESTRICTED_PLATFORM) || defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS)
#undef AZ_RESTRICTED_SECTION
#define SYSTEMINIT_CPP_SECTION_1 1
#define SYSTEMINIT_CPP_SECTION_2 2
#define SYSTEMINIT_CPP_SECTION_3 3
#define SYSTEMINIT_CPP_SECTION_4 4
@@ -168,30 +167,6 @@ void CryEngineSignalHandler(int signal)
#define LOCALIZATION_TRANSLATIONS_LIST_FILE_NAME "Libs/Localization/localization.xml"
//////////////////////////////////////////////////////////////////////////
#if defined(WIN32) || defined(LINUX) || defined(APPLE)
# define DLL_INITFUNC_RENDERER "PackageRenderConstructor"
# define DLL_INITFUNC_SOUND "CreateSoundSystem"
# define DLL_INITFUNC_FONT "CreateCryFontInterface"
# define DLL_INITFUNC_3DENGINE "CreateCry3DEngine"
# define DLL_INITFUNC_UI "CreateLyShineInterface"
#define AZ_RESTRICTED_SECTION_IMPLEMENTED
#elif defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_1
#include AZ_RESTRICTED_FILE(SystemInit_cpp)
#endif
#if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED)
#undef AZ_RESTRICTED_SECTION_IMPLEMENTED
#else
# define DLL_INITFUNC_RENDERER (LPCSTR)1
# define DLL_INITFUNC_RENDERER (LPCSTR)1
# define DLL_INITFUNC_SOUND (LPCSTR)1
# define DLL_INITFUNC_PHYSIC (LPCSTR)1
# define DLL_INITFUNC_FONT (LPCSTR)1
# define DLL_INITFUNC_3DENGINE (LPCSTR)1
# define DLL_INITFUNC_UI (LPCSTR)1
#endif
#define AZ_TRACE_SYSTEM_WINDOW AZ::Debug::Trace::GetDefaultSystemWindow()
#ifdef WIN32
@@ -285,96 +260,6 @@ static void CmdCrashTest(IConsoleCmdArgs* pArgs)
}
AZ_POP_DISABLE_WARNING
//////////////////////////////////////////////////////////////////////////
struct SysSpecOverrideSink
: public ILoadConfigurationEntrySink
{
virtual void OnLoadConfigurationEntry(const char* szKey, const char* szValue, const char* szGroup)
{
ICVar* pCvar = gEnv->pConsole->GetCVar(szKey);
if (pCvar)
{
const bool wasNotInConfig = ((pCvar->GetFlags() & VF_WASINCONFIG) == 0);
bool applyCvar = wasNotInConfig;
if (applyCvar == false)
{
// Special handling for sys_spec_full
if (azstricmp(szKey, "sys_spec_full") == 0)
{
// If it is set to 0 then ignore this request to set to something else
// If it is set to 0 then the user wants to changes system spec settings in system.cfg
if (pCvar->GetIVal() != 0)
{
applyCvar = true;
}
}
else
{
// This could bypass the restricted cvar checks that exist elsewhere depending on
// the calling code so we also need check here before setting.
bool isConst = pCvar->IsConstCVar();
bool isCheat = ((pCvar->GetFlags() & (VF_CHEAT | VF_CHEAT_NOCHECK | VF_CHEAT_ALWAYS_CHECK)) != 0);
bool isReadOnly = ((pCvar->GetFlags() & VF_READONLY) != 0);
bool isDeprecated = ((pCvar->GetFlags() & VF_DEPRECATED) != 0);
bool allowApplyCvar = true;
if ((isConst || isCheat || isReadOnly) || isDeprecated)
{
allowApplyCvar = !isDeprecated && (gEnv->pSystem->IsDevMode()) || (gEnv->IsEditor());
}
if ((allowApplyCvar) || ALLOW_CONST_CVAR_MODIFICATIONS)
{
applyCvar = true;
}
}
}
if (applyCvar)
{
pCvar->Set(szValue);
}
else
{
CryLogAlways("NOT VF_WASINCONFIG Ignoring cvar '%s' new value '%s' old value '%s' group '%s'", szKey, szValue, pCvar->GetString(), szGroup);
}
}
else
{
CryLogAlways("Can't find cvar '%s' value '%s' group '%s'", szKey, szValue, szGroup);
}
}
};
#if !defined(CONSOLE)
struct SysSpecOverrideSinkConsole
: public ILoadConfigurationEntrySink
{
virtual void OnLoadConfigurationEntry(const char* szKey, const char* szValue, const char* szGroup)
{
// Ignore platform-specific cvars that should just be executed on the console
if (azstricmp(szGroup, "Platform") == 0)
{
return;
}
ICVar* pCvar = gEnv->pConsole->GetCVar(szKey);
if (pCvar)
{
pCvar->Set(szValue);
}
else
{
// If the cvar doesn't exist, calling this function only saves the value in case it's registered later where
// at that point it will be set from the stored value. This is required because otherwise registering the
// cvar bypasses any callbacks and uses values directly from the cvar group files.
gEnv->pConsole->LoadConfigVar(szKey, szValue);
}
}
};
#endif
static ESystemConfigPlatform GetDevicePlatform()
{
#if defined(AZ_PLATFORM_WINDOWS) || defined(AZ_PLATFORM_LINUX)
@@ -398,98 +283,6 @@ static ESystemConfigPlatform GetDevicePlatform()
#endif
}
//////////////////////////////////////////////////////////////////////////
#if !defined(AZ_MONOLITHIC_BUILD)
AZStd::unique_ptr<AZ::DynamicModuleHandle> CSystem::LoadDynamiclibrary(const char* dllName) const
{
AZStd::unique_ptr<AZ::DynamicModuleHandle> handle = AZ::DynamicModuleHandle::Create(dllName);
bool libraryLoaded = handle->Load(false);
// We need to inject the environment first thing so that allocators are available immediately
InjectEnvironmentFunction injectEnv = handle->GetFunction<InjectEnvironmentFunction>(INJECT_ENVIRONMENT_FUNCTION);
if (injectEnv)
{
auto env = AZ::Environment::GetInstance();
injectEnv(env);
}
if (!libraryLoaded)
{
handle.release();
}
return handle;
}
//////////////////////////////////////////////////////////////////////////
AZStd::unique_ptr<AZ::DynamicModuleHandle> CSystem::LoadDLL(const char* dllName)
{
AZ_TracePrintf(AZ_TRACE_SYSTEM_WINDOW, "Loading DLL: %s", dllName);
AZStd::unique_ptr<AZ::DynamicModuleHandle> handle = LoadDynamiclibrary(dllName);
if (!handle)
{
#if defined(LINUX) || defined(APPLE)
AZ_Assert(false, "Error loading dylib: %s, error : %s\n", dllName, dlerror());
#else
AZ_Assert(false, "Error loading dll: %s, error code %d", dllName, GetLastError());
#endif
return handle;
}
return handle;
}
// TODO:DLL #endif //#if defined(AZ_HAS_DLL_SUPPORT) && !defined(AZ_MONOLITHIC_BUILD)
#endif //if !defined(AZ_MONOLITHIC_BUILD)
//////////////////////////////////////////////////////////////////////////
bool CSystem::LoadEngineDLLs()
{
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CSystem::UnloadDLL(const char* dllName)
{
bool isSuccess = false;
AZ::Crc32 key(dllName);
AZStd::unique_ptr<AZ::DynamicModuleHandle> empty;
AZStd::unique_ptr<AZ::DynamicModuleHandle>& hModule = stl::find_in_map_ref(m_moduleDLLHandles, key, empty);
if ((hModule) && (hModule->IsLoaded()))
{
DetachEnvironmentFunction detachEnv = hModule->GetFunction<DetachEnvironmentFunction>(DETACH_ENVIRONMENT_FUNCTION);
if (detachEnv)
{
detachEnv();
}
isSuccess = hModule->Unload();
hModule.release();
}
return isSuccess;
}
//////////////////////////////////////////////////////////////////////////
void CSystem::ShutdownModuleLibraries()
{
#if !defined(AZ_MONOLITHIC_BUILD)
for (auto iterator = m_moduleDLLHandles.begin(); iterator != m_moduleDLLHandles.end(); ++iterator)
{
if (iterator->second->IsLoaded())
{
iterator->second->Unload();
}
iterator->second.release();
}
m_moduleDLLHandles.clear();
#endif // !defined(AZ_MONOLITHIC_BUILD)
}
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
bool CSystem::InitConsole()
@@ -49,6 +49,12 @@ int main(int argc, char* argv[])
AZStd::unique_ptr<AzFramework::ProcessWatcher> shellProcess(AzFramework::ProcessWatcher::LaunchProcess(shellProcessLaunch, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE));
shellProcess->WaitForProcessToExit(120);
shellProcess.reset();
parameters = AZStd::string::format("-c \"%s/scripts/o3de.sh register --this-engine\"", enginePath.c_str());
shellProcessLaunch.m_commandlineParameters = parameters;
shellProcess.reset(AzFramework::ProcessWatcher::LaunchProcess(shellProcessLaunch, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE));
shellProcess->WaitForProcessToExit(120);
shellProcess.reset();
AZ::IO::FixedMaxPath projectManagerPath = installedBinariesFolder/"o3de.app"/"Contents"/"MacOS"/"o3de";
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
@@ -10,6 +10,8 @@
#include <QProcessEnvironment>
#include <QDir>
#include <AzCore/Utils/Utils.h>
namespace O3DE::ProjectManager
{
namespace ProjectUtils
@@ -94,5 +96,10 @@ namespace O3DE::ProjectManager
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script..."));
}
AZ::IO::FixedMaxPath GetEditorDirectory()
{
return AZ::Utils::GetExecutableDirectory();
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -11,6 +11,9 @@
#include <QStandardPaths>
#include <QDir>
#include <AzCore/Utils/Utils.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
namespace O3DE::ProjectManager
{
namespace ProjectUtils
@@ -104,5 +107,35 @@ namespace O3DE::ProjectManager
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script..."));
}
AZ::IO::FixedMaxPath GetEditorDirectory()
{
AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory();
AZ::IO::FixedMaxPath editorPath{ executableDirectory };
editorPath /= "../../../Editor.app/Contents/MacOS";
editorPath = editorPath.LexicallyNormal();
if (!AZ::IO::SystemFile::IsDirectory(editorPath.c_str()))
{
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if (AZ::IO::FixedMaxPath installedBinariesPath;
settingsRegistry->Get(installedBinariesPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
{
if (AZ::IO::FixedMaxPath engineRootFolder;
settingsRegistry->Get(engineRootFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
{
editorPath = engineRootFolder / installedBinariesPath / "Editor.app/Contents/MacOS";
}
}
}
if (!AZ::IO::SystemFile::IsDirectory(editorPath.c_str()))
{
AZ_Error("ProjectManager", false, "Unable to find the Editor app bundle!");
}
}
return editorPath;
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -14,6 +14,8 @@
#include <QProcess>
#include <QProcessEnvironment>
#include <AzCore/Utils/Utils.h>
namespace O3DE::ProjectManager
{
namespace ProjectUtils
@@ -139,5 +141,10 @@ namespace O3DE::ProjectManager
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script..."));
}
AZ::IO::FixedMaxPath GetEditorDirectory()
{
return AZ::Utils::GetExecutableDirectory();
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -14,6 +14,7 @@
#include <QWidget>
#include <QProcessEnvironment>
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/Outcome/Outcome.h>
namespace O3DE::ProjectManager
@@ -67,7 +68,8 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> GetProjectBuildPath(const QString& projectPath);
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath);
AZ::Outcome<QString, QString> RunGetPythonScript(const QString& enginePath);
AZ::IO::FixedMaxPath GetEditorDirectory();
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -392,11 +392,11 @@ namespace O3DE::ProjectManager
{
if (!WarnIfInBuildQueue(projectPath))
{
AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory();
AZ::IO::FixedMaxPath executableDirectory = ProjectUtils::GetEditorDirectory();
AZStd::string executableFilename = "Editor";
AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
auto cmdPath = AZ::IO::FixedMaxPathString::format(
"%s -regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(),
"%s --regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(),
projectPath.toStdString().c_str());
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
@@ -152,8 +152,6 @@ namespace AZ
ResultCode FrameScheduler::ImportScopeProducer(ScopeProducer& scopeProducer)
{
AZ_PROFILE_SCOPE(RHI, "FrameScheduler: ImportScopeProducer");
if (!ValidateIsProcessing())
{
return RHI::ResultCode::InvalidOperation;
@@ -266,7 +264,6 @@ namespace AZ
// Execute all queued resource invalidations, which will mark SRG's for compilation.
{
AZ_PROFILE_SCOPE(RHI, "Invalidate Resources");
ResourceInvalidateBus::ExecuteQueuedEvents();
}
@@ -216,8 +216,6 @@ namespace AZ
void RasterPass::CompileResources(const RHI::FrameGraphCompileContext& context)
{
AZ_PROFILE_SCOPE(RPI, "RasterPass: CompileResources");
if (m_shaderResourceGroup == nullptr)
{
return;
@@ -17,8 +17,6 @@ namespace Multiplayer
class IEntityDomain
{
public:
using EntitiesNotInDomain = AZStd::unordered_set<NetEntityId>;
virtual ~IEntityDomain() = default;
//! For domains that operate on a region of space, this sets the area the domain is responsible for.
@@ -34,12 +32,10 @@ namespace Multiplayer
//! @return false if this entity should not belong to the entity manger, true if it could be owned by the entity manager
virtual bool IsInDomain(const ConstNetworkEntityHandle& entityHandle) const = 0;
//! Enable Entity Domain Exit Tracking for entities on the host.
//! @param ownedEntitySet the set of entities to activate tracking for
virtual void ActivateTracking(const INetworkEntityManager::OwnedEntitySet& ownedEntitySet) = 0;
//! Return the set of netbound entities not included in this domain.
virtual const EntitiesNotInDomain& RetrieveEntitiesNotInDomain() const = 0;
//! This method will be invoked whenever we unexpectedly lose the authoritative entity replicator for an entity.
//! This gives our entity domain a chance to determine whether or not it should assume authority in this instance.
//! @param entityHandle the network entity handle of the entity that has lost its authoritative replicator
virtual void HandleLossOfAuthoritativeReplicator(const ConstNetworkEntityHandle& entityHandle) = 0;
//! Debug draw to visualize host entity domains.
virtual void DebugDraw() const = 0;
@@ -150,7 +150,6 @@ namespace Multiplayer
void ClearRemovedReplicators();
class OrphanedEntityRpcs
: public AzNetworking::ITimeoutHandler
{
public:
OrphanedEntityRpcs(EntityReplicationManager& replicationManager);
@@ -159,7 +158,6 @@ namespace Multiplayer
void AddOrphanedRpc(NetEntityId entityId, NetworkEntityRpcMessage& entityRpcMessage);
AZStd::size_t Size() const { return m_entityRpcMap.size(); }
private:
AzNetworking::TimeoutResult HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item) override;
struct OrphanedRpcs
{
OrphanedRpcs() = default;
@@ -26,6 +26,7 @@ namespace Multiplayer
using EntityExitDomainEvent = AZ::Event<const ConstNetworkEntityHandle&>;
using ControllersActivatedEvent = AZ::Event<const ConstNetworkEntityHandle&, EntityIsMigrating>;
using ControllersDeactivatedEvent = AZ::Event<const ConstNetworkEntityHandle&, EntityIsMigrating>;
using NetEntityIdSet = AZStd::unordered_set<NetEntityId>;
//! @class INetworkEntityManager
//! @brief The interface for managing all networked entities.
@@ -34,18 +35,17 @@ namespace Multiplayer
public:
AZ_RTTI(INetworkEntityManager, "{109759DE-9492-439C-A0B1-AE46E6FD029C}");
using OwnedEntitySet = AZStd::unordered_set<ConstNetworkEntityHandle>;
using EntityList = AZStd::vector<NetworkEntityHandle>;
virtual ~INetworkEntityManager() = default;
//! Configures the NetworkEntityManager to operate as an authoritative host.
//! @param hostId the hostId of this NetworkEntityManager
//! Configures the NetworkEntityManager.
//! @param hostId the hostId of this NetworkEntityManager (invalid for clients)
//! @param entityDomain the entity domain used to determine which entities this manager has authority over
virtual void Initialize(const HostId& hostId, AZStd::unique_ptr<IEntityDomain> entityDomain) = 0;
//! Returns whether or not the network entity manager has been initialized to host.
//! @return boolean true if this network entity manager has been intialized to host
//! Returns whether or not the network entity manager has been initialized.
//! @return boolean true if this network entity manager has been intialized
virtual bool IsInitialized() const = 0;
//! Returns the entity domain associated with this network entity manager, this will be nullptr on clients.
@@ -181,6 +181,19 @@ namespace Multiplayer
//! @param entityRpcMessage the local rpc message to handle
virtual void HandleLocalRpcMessage(NetworkEntityRpcMessage& message) = 0;
//! Handles a set of entities transitioning between entity domains.
//! @param entitiesNotInDomain the set of entities that are no longer contained within our entity domain
virtual void HandleEntitiesExitDomain(const NetEntityIdSet& entitiesNotInDomain) = 0;
//! Forcibly assumes authoritative control over the given entity.
//! This should only be used in the event of the unexpected loss of the previous authority, any other usage could corrupt the simulation.
//! @param entityHandle the entity to forcibly assume authoritative control over
virtual void ForceAssumeAuthority(const ConstNetworkEntityHandle& entityHandle) = 0;
//! Overrides the default timeout time used during entity migrations.
//! @param timeoutTimeMs the timeout time to use in milliseconds
virtual void SetMigrateTimeoutTimeMs(AZ::TimeMs timeoutTimeMs) = 0;
//! Visualization of network entity manager state.
virtual void DebugDraw() const = 0;
};
@@ -317,7 +317,7 @@ namespace Multiplayer
return false;
}
bool NetBindComponent::HandlePropertyChangeMessage([[maybe_unused]] AzNetworking::ISerializer& serializer, [[maybe_unused]] bool notifyChanges)
bool NetBindComponent::HandlePropertyChangeMessage(AzNetworking::ISerializer& serializer, bool notifyChanges)
{
const NetEntityRole netEntityRole = m_netEntityRole;
ReplicationRecord replicationRecord(netEntityRole);
@@ -492,7 +492,7 @@ namespace Multiplayer
void NetBindComponent::FillTotalReplicationRecord(ReplicationRecord& replicationRecord) const
{
replicationRecord.Append(m_totalRecord);
// if we have any outstanding changes yet to be logged, grab those as well
// If we have any outstanding changes yet to be logged, grab those as well
if (m_currentRecord.HasChanges())
{
replicationRecord.Append(m_currentRecord);
@@ -26,14 +26,9 @@ namespace Multiplayer
return true;
}
void FullOwnershipEntityDomain::ActivateTracking([[maybe_unused]] const INetworkEntityManager::OwnedEntitySet& ownedEntitySet)
void FullOwnershipEntityDomain::HandleLossOfAuthoritativeReplicator([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle)
{
;
}
const IEntityDomain::EntitiesNotInDomain& FullOwnershipEntityDomain::RetrieveEntitiesNotInDomain() const
{
return m_entitiesNotInDomain;
AZ_Assert(false, "FullOwnershipEntityDomain has authoritative control over all entities, something unexpected has happened");
}
void FullOwnershipEntityDomain::DebugDraw() const
@@ -24,12 +24,8 @@ namespace Multiplayer
void SetAabb(const AZ::Aabb& aabb) override;
const AZ::Aabb& GetAabb() const override;
bool IsInDomain(const ConstNetworkEntityHandle& entityHandle) const override;
void ActivateTracking(const INetworkEntityManager::OwnedEntitySet& ownedEntitySet) override;
const EntitiesNotInDomain& RetrieveEntitiesNotInDomain() const override;
void HandleLossOfAuthoritativeReplicator(const ConstNetworkEntityHandle& entityHandle) override;
void DebugDraw() const override;
//! @}
private:
EntitiesNotInDomain m_entitiesNotInDomain;
};
}
@@ -0,0 +1,41 @@
/*
* 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 <Source/EntityDomains/NullEntityDomain.h>
#include <Multiplayer/IMultiplayer.h>
#include <AzCore/Console/ILogger.h>
namespace Multiplayer
{
void NullEntityDomain::SetAabb([[maybe_unused]] const AZ::Aabb& aabb)
{
; // Do nothing, by definition we own everything
}
const AZ::Aabb& NullEntityDomain::GetAabb() const
{
static AZ::Aabb nullAabb = AZ::Aabb::CreateNull();
return nullAabb;
}
bool NullEntityDomain::IsInDomain([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) const
{
return false;
}
void NullEntityDomain::HandleLossOfAuthoritativeReplicator([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle)
{
AZLOG_ERROR("Timed out entity id %llu during migration, marking for removal", aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()));
GetNetworkEntityManager()->MarkForRemoval(entityHandle);
}
void NullEntityDomain::DebugDraw() const
{
;
}
}
@@ -0,0 +1,31 @@
/*
* 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 <Multiplayer/EntityDomains/IEntityDomain.h>
namespace Multiplayer
{
class NullEntityDomain
: public IEntityDomain
{
public:
NullEntityDomain() = default;
NullEntityDomain(const NullEntityDomain& rhs) = default;
//! IEntityDomain overrides.
//! @{
void SetAabb(const AZ::Aabb& aabb) override;
const AZ::Aabb& GetAabb() const override;
bool IsInDomain(const ConstNetworkEntityHandle& entityHandle) const override;
void HandleLossOfAuthoritativeReplicator(const ConstNetworkEntityHandle& entityHandle) override;
void DebugDraw() const override;
//! @}
};
}
@@ -13,6 +13,7 @@
#include <ConnectionData/ClientToServerConnectionData.h>
#include <ConnectionData/ServerToClientConnectionData.h>
#include <EntityDomains/FullOwnershipEntityDomain.h>
#include <EntityDomains/NullEntityDomain.h>
#include <ReplicationWindows/NullReplicationWindow.h>
#include <ReplicationWindows/ServerToClientReplicationWindow.h>
#include <Source/AutoGen/AutoComponentTypes.h>
@@ -831,18 +832,21 @@ namespace Multiplayer
if (multiplayerType == MultiplayerAgentType::ClientServer || multiplayerType == MultiplayerAgentType::DedicatedServer)
{
m_spawnNetboundEntities = true;
m_initEvent.Signal(m_networkInterface);
m_initEvent.Signal(m_networkInterface); //< Note! This might initialize our network entity manager for us
if (!m_networkEntityManager.IsInitialized())
{
// Set up a full ownership domain if we didn't construct a domain during the initialize event
const AZ::CVarFixedString serverAddr = cl_serveraddr;
const uint16_t serverPort = cl_serverport;
const AzNetworking::ProtocolType serverProtocol = sv_protocol;
const AzNetworking::IpAddress hostId = AzNetworking::IpAddress(serverAddr.c_str(), serverPort, serverProtocol);
// Set up a full ownership domain if we didn't construct a domain during the initialize event
m_networkEntityManager.Initialize(hostId, AZStd::make_unique<FullOwnershipEntityDomain>());
}
}
else if (multiplayerType == MultiplayerAgentType::Client)
{
m_networkEntityManager.Initialize(AzNetworking::IpAddress(), AZStd::make_unique<NullEntityDomain>());
}
}
m_agentType = multiplayerType;
@@ -1106,7 +1110,6 @@ namespace Multiplayer
void MultiplayerSystemComponent::OnAutonomousEntityReplicatorCreated()
{
m_autonomousEntityReplicatorCreatedHandler.Disconnect();
//m_networkEntityManager.GetNetworkEntityAuthorityTracker()->ResetTimeoutTime(AZ::TimeMs{ 2000 });
m_clientMigrationEndEvent.Signal();
}
@@ -155,7 +155,6 @@ namespace Multiplayer
AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session");
AzNetworking::INetworkInterface* m_networkInterface = nullptr;
AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr;
AZ::ConsoleCommandInvokedEvent::Handler m_consoleCommandHandler;
AZ::ThreadSafeDeque<AZStd::string> m_cvarCommands;
@@ -912,24 +912,22 @@ namespace Multiplayer
;
}
AzNetworking::TimeoutResult EntityReplicationManager::OrphanedEntityRpcs::HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item)
{
NetEntityId timedOutEntityId = aznumeric_cast<NetEntityId>(item.m_userData);
auto entityRpcsIter = m_entityRpcMap.find(timedOutEntityId);
if (entityRpcsIter != m_entityRpcMap.end())
{
for (NetworkEntityRpcMessage& rpcMessage : entityRpcsIter->second.m_rpcMessages)
{
m_replicationManager.DispatchOrphanedRpc(rpcMessage, nullptr);
}
m_entityRpcMap.erase(entityRpcsIter);
}
return AzNetworking::TimeoutResult::Delete;
}
void EntityReplicationManager::OrphanedEntityRpcs::Update()
{
m_timeoutQueue.UpdateTimeouts(*this);
m_timeoutQueue.UpdateTimeouts([this](AzNetworking::TimeoutQueue::TimeoutItem& item)
{
NetEntityId timedOutEntityId = aznumeric_cast<NetEntityId>(item.m_userData);
auto entityRpcsIter = m_entityRpcMap.find(timedOutEntityId);
if (entityRpcsIter != m_entityRpcMap.end())
{
for (NetworkEntityRpcMessage& rpcMessage : entityRpcsIter->second.m_rpcMessages)
{
m_replicationManager.DispatchOrphanedRpc(rpcMessage, nullptr);
}
m_entityRpcMap.erase(entityRpcsIter);
}
return AzNetworking::TimeoutResult::Delete;
});
}
bool EntityReplicationManager::OrphanedEntityRpcs::DispatchOrphanedRpcs(EntityReplicator& entityReplicator)
@@ -9,61 +9,53 @@
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/NetworkEntity/INetworkEntityManager.h>
#include <Multiplayer/EntityDomains/IEntityDomain.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/EBus/IEventScheduler.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
namespace Multiplayer
{
AZ_CVAR(AZ::TimeMs, net_EntityMigrationTimeoutMs, AZ::TimeMs{ 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time to wait for a new authority to attach to an entity before we delete the entity");
AZ_CVAR(AZ::TimeMs, net_DefaultEntityMigrationTimeoutMs, AZ::TimeMs{ 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time to wait for a new authority to attach to an entity before we delete the entity");
NetworkEntityAuthorityTracker::NetworkEntityAuthorityTracker(INetworkEntityManager& networkEntityManager)
: m_networkEntityManager(networkEntityManager)
, m_timeoutTimeMs(net_DefaultEntityMigrationTimeoutMs)
{
;
}
void NetworkEntityAuthorityTracker::SetTimeoutTimeMs(AZ::TimeMs timeoutTimeMs)
{
m_timeoutTimeMs = timeoutTimeMs;
}
bool NetworkEntityAuthorityTracker::AddEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, const HostId& newOwner)
{
bool ret = false;
auto timeoutData = m_timeoutDataMap.find(entityHandle.GetNetEntityId());
if (timeoutData != m_timeoutDataMap.end())
auto timeoutData = m_timedOutNetEntityIds.find(entityHandle.GetNetEntityId());
if (timeoutData != m_timedOutNetEntityIds.end())
{
AZLOG
(
NET_AuthTracker,
"AuthTracker: Removing timeout for networkEntityId %llu from %s, new owner is %s",
"AuthTracker: Removing timeout for networkEntityId %llu, new owner is %s",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
timeoutData->second.m_previousOwner.GetString().c_str(),
newOwner.GetString().c_str()
);
m_timeoutDataMap.erase(timeoutData);
m_timedOutNetEntityIds.erase(timeoutData);
ret = true;
}
auto iter = m_entityAuthorityMap.find(entityHandle.GetNetEntityId());
if (iter != m_entityAuthorityMap.end())
{
AZLOG
(
NET_AuthTracker,
"AuthTracker: Assigning networkEntityId %llu from %s to %s",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
iter->second.back().GetString().c_str(),
newOwner.GetString().c_str()
);
}
else
{
AZLOG
(
NET_AuthTracker,
"AuthTracker: Assigning networkEntityId %llu to %s",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
newOwner.GetString().c_str()
);
}
AZLOG
(
NET_AuthTracker,
"AuthTracker: Assigning networkEntityId %llu to %s",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
newOwner.GetString().c_str()
);
m_entityAuthorityMap[entityHandle.GetNetEntityId()].push_back(newOwner);
return ret;
@@ -103,14 +95,35 @@ namespace Multiplayer
{
AZ_Assert
(
(m_timeoutDataMap.find(entityHandle.GetNetEntityId()) == m_timeoutDataMap.end()) ||
(m_timeoutDataMap[entityHandle.GetNetEntityId()].m_previousOwner == previousOwner),
m_timedOutNetEntityIds.find(entityHandle.GetNetEntityId()) == m_timedOutNetEntityIds.end(),
"Trying to add something twice to the timeout map, this is unexpected"
);
m_timeoutQueue.RegisterItem(aznumeric_cast<uint64_t>(entityHandle.GetNetEntityId()), net_EntityMigrationTimeoutMs);
TimeoutData& timeoutData = m_timeoutDataMap[entityHandle.GetNetEntityId()];
timeoutData.m_entityHandle = entityHandle;
timeoutData.m_previousOwner = previousOwner;
m_timedOutNetEntityIds.insert(entityHandle.GetNetEntityId());
AZ::Interface<AZ::IEventScheduler>::Get()->AddCallback([this, netEntityId = entityHandle.GetNetEntityId()]
{
auto timeoutData = m_timedOutNetEntityIds.find(netEntityId);
if (timeoutData != m_timedOutNetEntityIds.end())
{
m_timedOutNetEntityIds.erase(timeoutData);
ConstNetworkEntityHandle entityHandle = m_networkEntityManager.GetEntity(netEntityId);
if (auto entity = entityHandle.GetEntity())
{
NetEntityRole networkRole = NetEntityRole::InvalidRole;
NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent();
if (netBindComponent != nullptr)
{
networkRole = netBindComponent->GetNetEntityRole();
}
if (networkRole != NetEntityRole::Authority)
{
m_networkEntityManager.GetEntityDomain()->HandleLossOfAuthoritativeReplicator(entityHandle);
}
}
}
},
AZ::Name("Entity authority removal functor"),
m_timeoutTimeMs
);
}
else
{
@@ -127,18 +140,6 @@ namespace Multiplayer
}
HostId NetworkEntityAuthorityTracker::GetEntityAuthorityManager(ConstNetworkEntityHandle entityHandle) const
{
HostId hostId = GetEntityAuthorityManagerInternal(entityHandle);
AZ_Assert(hostId != InvalidHostId, "Unable to determine manager for entity");
return hostId;
}
bool NetworkEntityAuthorityTracker::DoesEntityHaveOwner(ConstNetworkEntityHandle entityHandle) const
{
return InvalidHostId != GetEntityAuthorityManagerInternal(entityHandle);
}
HostId NetworkEntityAuthorityTracker::GetEntityAuthorityManagerInternal(ConstNetworkEntityHandle entityHandle) const
{
if (auto localEnt = entityHandle.GetEntity())
{
@@ -167,52 +168,8 @@ namespace Multiplayer
return InvalidHostId;
}
NetworkEntityAuthorityTracker::TimeoutData::TimeoutData(ConstNetworkEntityHandle entityHandle, const HostId& previousOwner)
: m_entityHandle(entityHandle)
, m_previousOwner(previousOwner)
bool NetworkEntityAuthorityTracker::DoesEntityHaveOwner(ConstNetworkEntityHandle entityHandle) const
{
;
}
NetworkEntityAuthorityTracker::NetworkEntityTimeoutFunctor::NetworkEntityTimeoutFunctor
(
NetworkEntityAuthorityTracker& networkEntityAuthorityTracker,
INetworkEntityManager& networkEntityManager
)
: m_networkEntityAuthorityTracker(networkEntityAuthorityTracker)
, m_networkEntityManager(networkEntityManager)
{
;
}
AzNetworking::TimeoutResult NetworkEntityAuthorityTracker::NetworkEntityTimeoutFunctor::HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item)
{
const NetEntityId netEntityId = aznumeric_cast<NetEntityId>(item.m_userData);
auto timeoutData = m_networkEntityAuthorityTracker.m_timeoutDataMap.find(netEntityId);
if (timeoutData != m_networkEntityAuthorityTracker.m_timeoutDataMap.end())
{
m_networkEntityAuthorityTracker.m_timeoutDataMap.erase(timeoutData);
ConstNetworkEntityHandle entityHandle = m_networkEntityManager.GetEntity(netEntityId);
if (auto entity = entityHandle.GetEntity())
{
NetEntityRole networkRole = NetEntityRole::InvalidRole;
NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent();
if (netBindComponent != nullptr)
{
networkRole = netBindComponent->GetNetEntityRole();
}
if (networkRole != NetEntityRole::Authority)
{
AZLOG_ERROR
(
"Timed out entity id %llu during migration previous owner %s, removing it",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
timeoutData->second.m_previousOwner.GetString().c_str()
);
m_networkEntityManager.MarkForRemoval(entityHandle);
}
}
}
return AzNetworking::TimeoutResult::Delete;
return InvalidHostId != GetEntityAuthorityManager(entityHandle);
}
}
@@ -13,6 +13,7 @@
#include <AzCore/std/containers/unordered_map.h>
#include <AzNetworking/DataStructures/TimeoutQueue.h>
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Multiplayer/NetworkEntity/INetworkEntityManager.h>
namespace Multiplayer
{
@@ -23,43 +24,21 @@ namespace Multiplayer
public:
NetworkEntityAuthorityTracker(INetworkEntityManager& networkEntityManager);
void SetTimeoutTimeMs(AZ::TimeMs timeoutTimeMs);
bool DoesEntityHaveOwner(ConstNetworkEntityHandle entityHandle) const;
bool AddEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, const HostId& newOwner);
void RemoveEntityAuthorityManager(ConstNetworkEntityHandle entityHandle, const HostId& previousOwner);
HostId GetEntityAuthorityManager(ConstNetworkEntityHandle entityHandle) const;
private:
HostId GetEntityAuthorityManagerInternal(ConstNetworkEntityHandle entityHandle) const;
NetworkEntityAuthorityTracker& operator= (const NetworkEntityAuthorityTracker&) = delete;
struct TimeoutData final
{
TimeoutData() = default;
TimeoutData(ConstNetworkEntityHandle entityHandle, const HostId& previousOwner);
ConstNetworkEntityHandle m_entityHandle;
HostId m_previousOwner = InvalidHostId;
};
struct NetworkEntityTimeoutFunctor final
: public AzNetworking::ITimeoutHandler
{
NetworkEntityTimeoutFunctor(NetworkEntityAuthorityTracker& networkEntityAuthorityTracker, INetworkEntityManager& m_networkEntityManager);
AzNetworking::TimeoutResult HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item) override;
private:
AZ_DISABLE_COPY_MOVE(NetworkEntityTimeoutFunctor);
NetworkEntityAuthorityTracker& m_networkEntityAuthorityTracker;
INetworkEntityManager& m_networkEntityManager;
};
using TimeoutDataMap = AZStd::unordered_map<NetEntityId, TimeoutData>;
using EntityAuthorityMap = AZStd::unordered_map<NetEntityId, AZStd::vector<HostId>>;
TimeoutDataMap m_timeoutDataMap;
NetEntityIdSet m_timedOutNetEntityIds;
EntityAuthorityMap m_entityAuthorityMap;
INetworkEntityManager& m_networkEntityManager;
AzNetworking::TimeoutQueue m_timeoutQueue;
AZ::TimeMs m_timeoutTimeMs = AZ::TimeMs{ 0 };
};
}
@@ -28,12 +28,10 @@
namespace Multiplayer
{
AZ_CVAR(bool, net_DebugCheckNetworkEntityManager, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables extra debug checks inside the NetworkEntityManager");
AZ_CVAR(AZ::TimeMs, net_EntityDomainUpdateMs, AZ::TimeMs{ 500 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Frequency for updating the entity domain in ms");
NetworkEntityManager::NetworkEntityManager()
: m_networkEntityAuthorityTracker(*this)
, m_removeEntitiesEvent([this] { RemoveEntities(); }, AZ::Name("NetworkEntityManager remove entities event"))
, m_updateEntityDomainEvent([this] { UpdateEntityDomain(); }, AZ::Name("NetworkEntityManager update entity domain event"))
{
AZ::Interface<INetworkEntityManager>::Register(this);
AzFramework::RootSpawnableNotificationBus::Handler::BusConnect();
@@ -63,8 +61,6 @@ namespace Multiplayer
}
m_entityDomain = AZStd::move(entityDomain);
m_updateEntityDomainEvent.Enqueue(net_EntityDomainUpdateMs, true);
m_entityDomain->ActivateTracking(m_ownedEntities);
}
bool NetworkEntityManager::IsInitialized() const
@@ -231,6 +227,74 @@ namespace Multiplayer
m_localDeferredRpcMessages.emplace_back(AZStd::move(message));
}
void NetworkEntityManager::HandleEntitiesExitDomain(const NetEntityIdSet& entitiesNotInDomain)
{
for (NetEntityId exitingId : entitiesNotInDomain)
{
bool safeToExit = true;
NetworkEntityHandle entityHandle = m_networkEntityTracker.Get(exitingId);
// We need special handling for the NetworkHierarchy as well, since related entities need to be migrated together
NetworkHierarchyRootComponentController* hierarchyRootController = entityHandle.FindController<NetworkHierarchyRootComponentController>();
NetworkHierarchyChildComponentController* hierarchyChildController = entityHandle.FindController<NetworkHierarchyChildComponentController>();
// Find the root entity
AZ::Entity* hierarchyRootEntity = nullptr;
if (hierarchyRootController)
{
hierarchyRootEntity = hierarchyRootController->GetParent().GetHierarchicalRoot();
}
else if (hierarchyChildController)
{
hierarchyRootEntity = hierarchyChildController->GetParent().GetHierarchicalRoot();
}
if (hierarchyRootEntity)
{
NetEntityId rootNetId = GetNetEntityIdById(hierarchyRootEntity->GetId());
ConstNetworkEntityHandle rootEntityHandle = GetEntity(rootNetId);
// Check if the root entity is still tracked by this authority
if (rootEntityHandle.Exists() && rootEntityHandle.GetNetBindComponent()->HasController())
{
safeToExit = false;
}
}
// Validate that we aren't already planning to remove this entity
if (safeToExit)
{
for (auto remoteEntityId : m_removeList)
{
if (remoteEntityId == remoteEntityId)
{
safeToExit = false;
}
}
}
if (safeToExit)
{
// Tell all the attached replicators for this entity that it's exited the domain
m_entityExitDomainEvent.Signal(entityHandle);
}
}
}
void NetworkEntityManager::ForceAssumeAuthority(const ConstNetworkEntityHandle& entityHandle)
{
NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent();
if (netBindComponent != nullptr)
{
netBindComponent->ConstructControllers();
}
}
void NetworkEntityManager::SetMigrateTimeoutTimeMs(AZ::TimeMs timeoutTimeMs)
{
m_networkEntityAuthorityTracker.SetTimeoutTimeMs(timeoutTimeMs);
}
void NetworkEntityManager::DebugDraw() const
{
AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus;
@@ -243,7 +307,7 @@ namespace Multiplayer
NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity);
AZ::Aabb entityBounds = AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->GetEntityWorldBoundsUnion(entity->GetId());
entityBounds.Expand(AZ::Vector3(0.01f));
if (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority)
if ((netBindComponent != nullptr) && netBindComponent->GetNetEntityRole() == NetEntityRole::Authority)
{
debugDisplay->SetColor(AZ::Colors::Black);
debugDisplay->SetAlpha(0.5f);
@@ -277,77 +341,11 @@ namespace Multiplayer
m_localDeferredRpcMessages.clear();
}
void NetworkEntityManager::UpdateEntityDomain()
{
if (m_entityDomain == nullptr)
{
return;
}
const IEntityDomain::EntitiesNotInDomain& entitiesNotInDomain = m_entityDomain->RetrieveEntitiesNotInDomain();
for (NetEntityId exitingId : entitiesNotInDomain)
{
OnEntityExitDomain(exitingId);
}
}
void NetworkEntityManager::OnEntityExitDomain(NetEntityId entityId)
{
bool safeToExit = true;
NetworkEntityHandle entityHandle = m_networkEntityTracker.Get(entityId);
// We also need special handling for the NetworkHierarchy as well, since related entities need to be migrated together
NetworkHierarchyRootComponentController* hierarchyRootController = entityHandle.FindController<NetworkHierarchyRootComponentController>();
NetworkHierarchyChildComponentController* hierarchyChildController = entityHandle.FindController<NetworkHierarchyChildComponentController>();
// Find the root entity
AZ::Entity* hierarchyRootEntity = nullptr;
if (hierarchyRootController)
{
hierarchyRootEntity = hierarchyRootController->GetParent().GetHierarchicalRoot();
}
else if (hierarchyChildController)
{
hierarchyRootEntity = hierarchyChildController->GetParent().GetHierarchicalRoot();
}
if (hierarchyRootEntity)
{
NetEntityId rootNetId = GetNetEntityIdById(hierarchyRootEntity->GetId());
ConstNetworkEntityHandle rootEntityHandle = GetEntity(rootNetId);
// Check if the root entity is still tracked by this authority
if (rootEntityHandle.Exists() && rootEntityHandle.GetNetBindComponent()->HasController())
{
safeToExit = false;
}
}
// Validate that we aren't already planning to remove this entity
if (safeToExit)
{
for (auto remoteEntityId : m_removeList)
{
if (remoteEntityId == remoteEntityId)
{
safeToExit = false;
}
}
}
if (safeToExit)
{
m_entityExitDomainEvent.Signal(entityHandle);
}
}
void NetworkEntityManager::Reset()
{
m_multiplayerComponentRegistry.Reset();
m_removeList.clear();
m_entityDomain = nullptr;
m_updateEntityDomainEvent.RemoveFromQueue();
m_ownedEntities.clear();
m_entityExitDomainEvent.DisconnectAllHandlers();
m_onEntityMarkedDirty.DisconnectAllHandlers();
m_onEntityNotifyChanges.DisconnectAllHandlers();
@@ -79,12 +79,13 @@ namespace Multiplayer
void NotifyControllersActivated(const ConstNetworkEntityHandle& entityHandle, EntityIsMigrating entityIsMigrating) override;
void NotifyControllersDeactivated(const ConstNetworkEntityHandle& entityHandle, EntityIsMigrating entityIsMigrating) override;
void HandleLocalRpcMessage(NetworkEntityRpcMessage& message) override;
void HandleEntitiesExitDomain(const NetEntityIdSet& entitiesNotInDomain) override;
void ForceAssumeAuthority(const ConstNetworkEntityHandle& entityHandle) override;
void SetMigrateTimeoutTimeMs(AZ::TimeMs timeoutTimeMs) override;
void DebugDraw() const override;
//! @}
void DispatchLocalDeferredRpcMessages();
void UpdateEntityDomain();
void OnEntityExitDomain(NetEntityId entityId);
//! RootSpawnableNotificationBus
//! @{
@@ -106,9 +107,6 @@ namespace Multiplayer
AZ::ScheduledEvent m_removeEntitiesEvent;
AZStd::vector<NetEntityId> m_removeList;
AZStd::unique_ptr<IEntityDomain> m_entityDomain;
AZ::ScheduledEvent m_updateEntityDomainEvent;
OwnedEntitySet m_ownedEntities;
EntityExitDomainEvent m_entityExitDomainEvent;
AZ::Event<> m_onEntityMarkedDirty;
@@ -212,7 +212,7 @@ namespace Multiplayer
}
};
class BenchmarkNetworkEntityManager : public Multiplayer::INetworkEntityManager
class BenchmarkNetworkEntityManager : public MockNetworkEntityManager
{
public:
BenchmarkNetworkEntityManager() : m_authorityTracker(*this) {}
@@ -221,55 +221,6 @@ namespace Multiplayer
NetworkEntityAuthorityTracker* GetNetworkEntityAuthorityTracker() override { return &m_authorityTracker; }
MultiplayerComponentRegistry* GetMultiplayerComponentRegistry() override { return &m_multiplayerComponentRegistry; }
const HostId& GetHostId() const override { return m_hostId; }
EntityList CreateEntitiesImmediate(
[[maybe_unused]] const PrefabEntityId& prefabEntryId,
[[maybe_unused]] NetEntityRole netEntityRole,
[[maybe_unused]] const AZ::Transform& transform,
[[maybe_unused]] AutoActivate autoActivate) override {
return {};
}
EntityList CreateEntitiesImmediate(
[[maybe_unused]] const PrefabEntityId& prefabEntryId,
[[maybe_unused]] NetEntityId netEntityId,
[[maybe_unused]] NetEntityRole netEntityRole,
[[maybe_unused]] AutoActivate autoActivate,
[[maybe_unused]] const AZ::Transform& transform) override {
return {};
}
void SetupNetEntity(
[[maybe_unused]] AZ::Entity* netEntity,
[[maybe_unused]] PrefabEntityId prefabEntityId,
[[maybe_unused]] NetEntityRole netEntityRole) override {}
uint32_t GetEntityCount() const override { return {}; }
void MarkForRemoval(
[[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) override {}
bool IsMarkedForRemoval(
[[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) const override {
return {};
}
void ClearEntityFromRemovalList(
[[maybe_unused]] const ConstNetworkEntityHandle& entityHandle) override {}
void ClearAllEntities() override {}
void AddEntityMarkedDirtyHandler(
[[maybe_unused]] AZ::Event<>::Handler& entityMarkedDirtyHandle) override {}
void AddEntityNotifyChangesHandler(
[[maybe_unused]] AZ::Event<>::Handler& entityNotifyChangesHandle) override {}
void AddEntityExitDomainHandler(
[[maybe_unused]] EntityExitDomainEvent::Handler& entityExitDomainHandler) override {}
void AddControllersActivatedHandler(
[[maybe_unused]] ControllersActivatedEvent::Handler& controllersActivatedHandler) override {}
void AddControllersDeactivatedHandler(
[[maybe_unused]] ControllersDeactivatedEvent::Handler& controllersDeactivatedHandler) override {}
void NotifyEntitiesDirtied() override {}
void NotifyEntitiesChanged() override {}
void NotifyControllersActivated(
[[maybe_unused]] const ConstNetworkEntityHandle& entityHandle,
[[maybe_unused]] EntityIsMigrating entityIsMigrating) override {}
void NotifyControllersDeactivated(
[[maybe_unused]] const ConstNetworkEntityHandle& entityHandle,
[[maybe_unused]] EntityIsMigrating entityIsMigrating) override {}
void HandleLocalRpcMessage(
[[maybe_unused]] NetworkEntityRpcMessage& message) override {}
mutable AZStd::map<NetEntityId, AZ::Entity*> m_networkEntityMap;
@@ -298,18 +249,6 @@ namespace Multiplayer
return InvalidNetEntityId;
}
[[nodiscard]] AZStd::unique_ptr<AzFramework::EntitySpawnTicket> RequestNetSpawnableInstantiation(
[[maybe_unused]] const AZ::Data::Asset<AzFramework::Spawnable>& netSpawnable,
[[maybe_unused]] const AZ::Transform& transform) override
{
return {};
}
void Initialize([[maybe_unused]] const HostId& hostId, [[maybe_unused]] AZStd::unique_ptr<IEntityDomain> entityDomain) override {}
bool IsInitialized() const override { return true; }
IEntityDomain* GetEntityDomain() const override { return nullptr; }
void DebugDraw() const override {}
NetworkEntityTracker m_tracker;
NetworkEntityAuthorityTracker m_authorityTracker;
MultiplayerComponentRegistry m_multiplayerComponentRegistry;
@@ -10,6 +10,7 @@
#include <IMultiplayerConnectionMock.h>
#include <MockInterfaces.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/EBus/EventSchedulerSystemComponent.h>
#include <AzCore/Console/Console.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Name/Name.h>
@@ -119,6 +120,8 @@ namespace Multiplayer
m_mockTime = AZStd::make_unique<NiceMock<MockTime>>();
AZ::Interface<AZ::ITime>::Register(m_mockTime.get());
m_eventScheduler = AZStd::make_unique<AZ::EventSchedulerSystemComponent>();
m_mockNetworkTime = AZStd::make_unique<NiceMock<MockNetworkTime>>();
AZ::Interface<INetworkTime>::Register(m_mockNetworkTime.get());
@@ -170,6 +173,7 @@ namespace Multiplayer
AZ::Interface<IMultiplayer>::Unregister(m_mockMultiplayer.get());
AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(m_mockComponentApplicationRequests.get());
m_eventScheduler.reset();
m_mockTime.reset();
m_mockNetworkEntityManager.reset();
@@ -204,6 +208,7 @@ namespace Multiplayer
AZStd::unique_ptr<NiceMock<MockMultiplayer>> m_mockMultiplayer;
AZStd::unique_ptr<MockNetworkEntityManager> m_mockNetworkEntityManager;
AZStd::unique_ptr<NiceMock<MockTime>> m_mockTime;
AZStd::unique_ptr<AZ::EventSchedulerSystemComponent> m_eventScheduler;
AZStd::unique_ptr<NiceMock<MockNetworkTime>> m_mockNetworkTime;
AZStd::unique_ptr<NiceMock<IMultiplayerConnectionMock>> m_mockConnection;
@@ -87,6 +87,9 @@ namespace UnitTest
MOCK_METHOD2(NotifyControllersActivated, void(const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::EntityIsMigrating));
MOCK_METHOD2(NotifyControllersDeactivated, void(const Multiplayer::ConstNetworkEntityHandle&, Multiplayer::EntityIsMigrating));
MOCK_METHOD1(HandleLocalRpcMessage, void(Multiplayer::NetworkEntityRpcMessage&));
MOCK_METHOD1(HandleEntitiesExitDomain, void(const Multiplayer::NetEntityIdSet&));
MOCK_METHOD1(ForceAssumeAuthority, void(const Multiplayer::ConstNetworkEntityHandle&));
MOCK_METHOD1(SetMigrateTimeoutTimeMs, void(AZ::TimeMs));
MOCK_CONST_METHOD0(DebugDraw, void());
};
@@ -95,6 +95,8 @@ set(FILES
Source/Editor/MultiplayerEditorConnection.h
Source/EntityDomains/FullOwnershipEntityDomain.cpp
Source/EntityDomains/FullOwnershipEntityDomain.h
Source/EntityDomains/NullEntityDomain.cpp
Source/EntityDomains/NullEntityDomain.h
Source/MultiplayerStats.cpp
Source/MultiplayerSystemComponent.cpp
Source/MultiplayerSystemComponent.h
@@ -139,11 +139,20 @@ endif()
# Configure system includes
ly_set(LY_CXX_SYSTEM_INCLUDE_CONFIGURATION_FLAG
/experimental:external # Turns on "external" headers feature for MSVC compilers
/experimental:external # Turns on "external" headers feature for MSVC compilers, required for MSVC < 16.10
/external:W0 # Set warning level in external headers to 0. This is used to suppress warnings 3rdParty libraries which uses the "system_includes" option in their json configuration
)
# CMake 3.22rc added a definition for CMAKE_INCLUDE_SYSTEM_FLAG_CXX. However, its defined as "-external:I ", that space causes
# issues when trying to use in TargetIncludeSystemDirectories_unsupported.cmake.
# CMake 3.22rc has also not added support for external directories in MSVC through target_include_directories(... SYSTEM
# So we will just fix the flag that was added by 3.22rc so it works with our TargetIncludeSystemDirectories_unsupported.cmake
# Once target_include_directories(... SYSTEM is supported, we can branch and use TargetIncludeSystemDirectories_supported.cmake
# Reported this here: https://gitlab.kitware.com/cmake/cmake/-/issues/17904#note_1078281
if(NOT CMAKE_INCLUDE_SYSTEM_FLAG_CXX)
ly_set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX /external:I)
ly_set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX "/external:I")
else()
string(STRIP ${CMAKE_INCLUDE_SYSTEM_FLAG_CXX} CMAKE_INCLUDE_SYSTEM_FLAG_CXX)
endif()
include(cmake/Platform/Common/TargetIncludeSystemDirectories_unsupported.cmake)
+9 -7
View File
@@ -271,7 +271,7 @@ def CheckoutRepo(boolean disableSubmodules = false) {
palRm('commitdate')
}
def HandleDriveMount(String snapshot, String repositoryName, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean recreateVolume = false) {
def HandleDriveMount(String snapshot, String repositoryName, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean recreateVolume = false) {
unstash name: 'incremental_build_script'
def pythonCmd = ''
@@ -435,7 +435,7 @@ def ExportTestScreenshots(Map options, String branchName, String platformName, S
}
}
def UploadAPLogs(Map options, String branchName, String jobName, String workspace, Map params) {
def UploadAPLogs(Map options, String branchName, String platformName, String jobName, String workspace, Map params) {
dir("${workspace}/${ENGINE_REPOSITORY_NAME}") {
projects = params.CMAKE_LY_PROJECTS.split(",")
projects.each{ project ->
@@ -449,7 +449,7 @@ def UploadAPLogs(Map options, String branchName, String jobName, String workspac
}
def command = "${pythonPath} -u ${s3UploadScriptPath} --base_dir ${apLogsPath} " +
"--file_regex \".*\" --bucket ${env.AP_LOGS_S3_BUCKET} " +
"--search_subdirectories True --key_prefix ${env.JENKINS_JOB_NAME}/${branchName}/${env.BUILD_NUMBER}/${jobName} " +
"--search_subdirectories True --key_prefix ${env.JENKINS_JOB_NAME}/${branchName}/${env.BUILD_NUMBER}/${platformName}/${jobName} " +
'--extra_args {\\"ACL\\":\\"bucket-owner-full-control\\"}'
palSh(command, "Uploading AP logs for job ${jobName} for branch ${branchName}", false)
}
@@ -519,10 +519,10 @@ def CreateExportTestScreenshotsStage(Map pipelineConfig, String branchName, Stri
}
}
def CreateUploadAPLogsStage(Map pipelineConfig, String branchName, String jobName, String workspace, Map params) {
def CreateUploadAPLogsStage(Map pipelineConfig, String branchName, String platformName, String jobName, String workspace, Map params) {
return {
stage("${jobName}_upload_ap_logs") {
UploadAPLogs(pipelineConfig, branchName, jobName, workspace, params)
UploadAPLogs(pipelineConfig, branchName, platformName, jobName, workspace, params)
}
}
}
@@ -551,9 +551,11 @@ def CreateSingleNode(Map pipelineConfig, def platform, def build_job, Map envVar
CreateSetupStage(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platform.key, build_job.key, envVars, onlyMountEBSVolume).call()
if(build_job.value.steps) { //this is a pipe with many steps so create all the build stages
pipelineEnvVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, build_job.value.PIPELINE_ENV ?: EMPTY_JSON, pipelineName)
build_job.value.steps.each { build_step ->
build_job_name = build_step
envVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, platform.value.build_types[build_step].PIPELINE_ENV ?: EMPTY_JSON, pipelineName)
// This addition of maps makes it that the right operand will override entries if they overlap with the left operand
envVars = pipelineEnvVars + GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, platform.value.build_types[build_step].PIPELINE_ENV ?: EMPTY_JSON, pipelineName)
try {
CreateBuildStage(pipelineConfig, platform.key, build_step, envVars).call()
}
@@ -577,7 +579,7 @@ def CreateSingleNode(Map pipelineConfig, def platform, def build_job, Map envVar
}
}
if (IsAPLogUpload(branchName, build_job_name)) {
CreateUploadAPLogsStage(pipelineConfig, branchName, build_job_name, envVars['WORKSPACE'], platform.value.build_types[build_job_name].PARAMETERS).call()
CreateUploadAPLogsStage(pipelineConfig, branchName, platform.key, build_job_name, envVars['WORKSPACE'], platform.value.build_types[build_job_name].PARAMETERS).call()
}
// All other errors will be raised outside the retry block
currentResult = envVars['ON_FAILURE_MARK'] ?: 'FAILURE'
@@ -214,5 +214,48 @@
"CMAKE_LY_PROJECTS": "AutomatedTesting",
"CMAKE_TARGET": "all"
}
},
"install_profile": {
"TAGS": [],
"COMMAND": "build_linux.sh",
"PARAMETERS": {
"CONFIGURATION": "profile",
"OUTPUT_DIRECTORY": "build/linux",
"CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4 -DLY_DISABLE_TEST_MODULES=TRUE",
"CMAKE_TARGET": "install"
}
},
"install_profile_pipe": {
"TAGS": [
"nightly-incremental",
"nightly-clean"
],
"PIPELINE_ENV": {
"PROJECT_REPOSITORY_NAME": "TestProject"
},
"steps": [
"install_profile",
"project_generate",
"project_engineinstall_profile"
]
},
"project_generate": {
"TAGS": [],
"COMMAND": "python_linux.sh",
"PARAMETERS": {
"SCRIPT_PATH": "install/scripts/o3de.py",
"SCRIPT_PARAMETERS": "create-project -pp ${WORKSPACE}/${PROJECT_REPOSITORY_NAME} --force"
}
},
"project_engineinstall_profile": {
"TAGS": [],
"COMMAND": "build_linux.sh",
"PARAMETERS": {
"COMMAND_CWD": "${WORKSPACE}/${PROJECT_REPOSITORY_NAME}",
"CONFIGURATION": "profile",
"OUTPUT_DIRECTORY": "build/linux",
"CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4 -DCMAKE_MODULE_PATH=${WORKSPACE}/o3de/install/cmake",
"CMAKE_TARGET": "all"
}
}
}
+4 -1
View File
@@ -17,7 +17,10 @@ SOURCE_DIRECTORY=${PWD}
pushd $OUTPUT_DIRECTORY
LAST_CONFIGURE_CMD_FILE=ci_last_configure_cmd.txt
CONFIGURE_CMD="cmake ${SOURCE_DIRECTORY} ${CMAKE_OPTIONS} ${EXTRA_CMAKE_OPTIONS} -DLY_3RDPARTY_PATH=${LY_3RDPARTY_PATH} -DLY_PROJECTS='${CMAKE_LY_PROJECTS}'"
CONFIGURE_CMD="cmake ${SOURCE_DIRECTORY} ${CMAKE_OPTIONS} ${EXTRA_CMAKE_OPTIONS} -DLY_3RDPARTY_PATH=${LY_3RDPARTY_PATH}"
if [[ -n "$CMAKE_LY_PROJECTS" ]]; then
CONFIGURE_CMD="${CONFIGURE_CMD} -DLY_PROJECTS='${CMAKE_LY_PROJECTS}'"
fi
if [[ ! -e "CMakeCache.txt" ]]; then
echo [ci_build] First run, generating
RUN_CONFIGURE=1
@@ -18,3 +18,8 @@ if ! command -v ninja &> /dev/null; then
echo "[ci_build] Ninja not found"
exit 1
fi
if [[ -n "${COMMAND_CWD}" ]]; then
echo $(eval echo [ci_build] Changing CWD to $COMMAND_CWD)
cd $(eval echo ${COMMAND_CWD})
fi
@@ -162,5 +162,50 @@
"SCRIPT_PATH": "scripts/build/package/package.py",
"SCRIPT_PARAMETERS": "--platform Mac --type all"
}
},
"install_profile": {
"TAGS": [],
"COMMAND": "build_mac.sh",
"PARAMETERS": {
"CONFIGURATION": "profile",
"OUTPUT_DIRECTORY": "build/mac",
"CMAKE_OPTIONS": "-G Xcode -DLY_DISABLE_TEST_MODULES=TRUE",
"CMAKE_LY_PROJECTS": "",
"CMAKE_TARGET": "install"
}
},
"install_profile_pipe": {
"TAGS": [
"nightly-incremental",
"nightly-clean"
],
"PIPELINE_ENV": {
"PROJECT_REPOSITORY_NAME": "TestProject"
},
"steps": [
"install_profile",
"project_generate",
"project_engineinstall_profile"
]
},
"project_generate": {
"TAGS": [],
"COMMAND": "python_mac.sh",
"PARAMETERS": {
"SCRIPT_PATH": "install/O3DE_SDK.app/Contents/Engine/scripts/o3de.py",
"SCRIPT_PARAMETERS": "create-project -pp ${WORKSPACE}/${PROJECT_REPOSITORY_NAME} --force"
}
},
"project_engineinstall_profile": {
"TAGS": [],
"COMMAND": "build_mac.sh",
"PARAMETERS": {
"COMMAND_CWD": "${WORKSPACE}/${PROJECT_REPOSITORY_NAME}",
"CONFIGURATION": "profile",
"OUTPUT_DIRECTORY": "build/mac",
"CMAKE_OPTIONS": "-G Xcode -DCMAKE_MODULE_PATH=${WORKSPACE}/o3de/install/O3DE_SDK.app/Contents/Engine/cmake",
"CMAKE_LY_PROJECTS": "",
"CMAKE_TARGET": "ALL_BUILD"
}
}
}
+4 -1
View File
@@ -17,7 +17,10 @@ SOURCE_DIRECTORY=${PWD}
pushd $OUTPUT_DIRECTORY
LAST_CONFIGURE_CMD_FILE=ci_last_configure_cmd.txt
CONFIGURE_CMD="cmake ${SOURCE_DIRECTORY} ${CMAKE_OPTIONS} ${EXTRA_CMAKE_OPTIONS} -DLY_3RDPARTY_PATH=${LY_3RDPARTY_PATH} -DLY_PROJECTS='${CMAKE_LY_PROJECTS}'"
CONFIGURE_CMD="cmake ${SOURCE_DIRECTORY} ${CMAKE_OPTIONS} ${EXTRA_CMAKE_OPTIONS} -DLY_3RDPARTY_PATH=${LY_3RDPARTY_PATH}"
if [[ -n "$CMAKE_LY_PROJECTS" ]]; then
CONFIGURE_CMD="${CONFIGURE_CMD} -DLY_PROJECTS='${CMAKE_LY_PROJECTS}'"
fi
if [[ ! -e "CMakeCache.txt" ]]; then
echo [ci_build] First run, generating
RUN_CONFIGURE=1
+5
View File
@@ -13,3 +13,8 @@ if ! command -v cmake &> /dev/null; then
echo "[ci_build] CMake not found"
exit 1
fi
if [[ -n "${COMMAND_CWD}" ]]; then
echo $(eval echo [ci_build] Changing CWD to $COMMAND_CWD)
cd $(eval echo ${COMMAND_CWD})
fi
@@ -56,7 +56,7 @@
"COMMAND": "python_windows.cmd",
"PARAMETERS": {
"SCRIPT_PATH": "scripts/build/ci_build_metrics.py",
"SCRIPT_PARAMETERS": "--platform=Windows --repository=!REPOSITORY_NAME! --jobname=!JOB_NAME! --jobnumber=!BUILD_NUMBER! --jobnode=!NODE_LABEL! --changelist=!CHANGE_ID!"
"SCRIPT_PARAMETERS": "--platform=Windows --repository=%REPOSITORY_NAME% --jobname=%JOB_NAME% --jobnumber=%BUILD_NUMBER% --jobnode=%NODE_LABEL% --changelist=%CHANGE_ID%"
}
},
"windows_packaging_all": {
@@ -88,7 +88,7 @@
"CONFIGURATION": "profile",
"SCRIPT_PATH": "scripts/build/TestImpactAnalysis/tiaf_driver.py",
"SCRIPT_PARAMETERS":
"--config=\"!OUTPUT_DIRECTORY!/bin/TestImpactFramework/profile/Persistent/tiaf.json\" --src-branch=!BRANCH_NAME! --dst-branch=!CHANGE_TARGET! --commit=!CHANGE_ID! --s3-bucket=!TEST_IMPACT_S3_BUCKET! --mars-index-prefix=jonawals --s3-top-level-dir=!REPOSITORY_NAME! --build-number=!BUILD_NUMBER! --suite=main --test-failure-policy=continue"
"--config=\"%OUTPUT_DIRECTORY%/bin/TestImpactFramework/profile/Persistent/tiaf.json\" --src-branch=%BRANCH_NAME% --dst-branch=%CHANGE_TARGET% --commit=%CHANGE_ID% --s3-bucket=%TEST_IMPACT_S3_BUCKET% --mars-index-prefix=jonawals --s3-top-level-dir=%REPOSITORY_NAME% --build-number=%BUILD_NUMBER% --suite=main --test-failure-policy=continue"
}
},
"debug_vs2019": {
@@ -131,7 +131,7 @@
"PARAMETERS": {
"CONFIGURATION": "profile",
"OUTPUT_DIRECTORY": "build\\windows_vs2019",
"CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_TEST_IMPACT_INSTRUMENTATION_BIN=!TEST_IMPACT_WIN_BINARY!",
"CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_TEST_IMPACT_INSTRUMENTATION_BIN=%TEST_IMPACT_WIN_BINARY%",
"CMAKE_LY_PROJECTS": "AutomatedTesting",
"CMAKE_TARGET": "ALL_BUILD",
"CMAKE_NATIVE_BUILD_ARGS": "/m /nologo"
@@ -337,16 +337,12 @@
}
},
"install_profile_vs2019": {
"TAGS": [
"nightly-incremental",
"nightly-clean"
],
"TAGS": [],
"COMMAND": "build_windows.cmd",
"PARAMETERS": {
"CONFIGURATION": "profile",
"OUTPUT_DIRECTORY": "build\\windows_vs2019",
"CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_DISABLE_TEST_MODULES=TRUE",
"CMAKE_LY_PROJECTS": "",
"CMAKE_TARGET": "INSTALL",
"CMAKE_NATIVE_BUILD_ARGS": "/m /nologo"
}
@@ -363,14 +359,35 @@
"PARAMETERS": {
"CONFIGURATION": "profile",
"OUTPUT_DIRECTORY": "build\\windows_vs2019",
"CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_DISABLE_TEST_MODULES=TRUE -DLY_VERSION_ENGINE_NAME=o3de-sdk -DLY_INSTALLER_WIX_ROOT=\"!WIX! \"",
"EXTRA_CMAKE_OPTIONS": "-DLY_INSTALLER_AUTO_GEN_TAG=ON -DLY_INSTALLER_DOWNLOAD_URL=!INSTALLER_DOWNLOAD_URL! -DLY_INSTALLER_LICENSE_URL=!INSTALLER_DOWNLOAD_URL!/license",
"CPACK_BUCKET": "!INSTALLER_BUCKET!",
"CMAKE_LY_PROJECTS": "",
"CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_DISABLE_TEST_MODULES=TRUE -DLY_VERSION_ENGINE_NAME=o3de-sdk -DLY_INSTALLER_WIX_ROOT=\"%WIX% \"",
"EXTRA_CMAKE_OPTIONS": "-DLY_INSTALLER_AUTO_GEN_TAG=ON -DLY_INSTALLER_DOWNLOAD_URL=%INSTALLER_DOWNLOAD_URL% -DLY_INSTALLER_LICENSE_URL=%INSTALLER_DOWNLOAD_URL%/license",
"CPACK_BUCKET": "%INSTALLER_BUCKET%",
"CMAKE_TARGET": "ALL_BUILD",
"CMAKE_NATIVE_BUILD_ARGS": "/m /nologo"
}
},
"install_profile_vs2019_pipe": {
"TAGS": [
"nightly-incremental",
"nightly-clean"
],
"PIPELINE_ENV": {
"PROJECT_REPOSITORY_NAME": "TestProject"
},
"steps": [
"install_profile_vs2019",
"project_generate",
"project_engineinstall_profile_vs2019"
]
},
"project_generate": {
"TAGS": [],
"COMMAND": "python_windows.cmd",
"PARAMETERS": {
"SCRIPT_PATH": "install\\scripts\\o3de.py",
"SCRIPT_PARAMETERS": "create-project -pp %WORKSPACE%\\%PROJECT_REPOSITORY_NAME% --force"
}
},
"project_enginesource_profile_vs2019": {
"TAGS": [
"project"
@@ -382,8 +399,7 @@
"PARAMETERS": {
"CONFIGURATION": "profile",
"OUTPUT_DIRECTORY": "build\\windows_vs2019",
"CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_MODULE_PATH=!WORKSPACE!/o3de/cmake",
"CMAKE_LY_PROJECTS": "",
"CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_MODULE_PATH=%WORKSPACE%/o3de/cmake",
"CMAKE_TARGET": "ALL_BUILD",
"CMAKE_NATIVE_BUILD_ARGS": "/m /nologo"
}
@@ -395,10 +411,10 @@
},
"COMMAND": "build_windows.cmd",
"PARAMETERS": {
"COMMAND_CWD": "%WORKSPACE%\\%PROJECT_REPOSITORY_NAME%",
"CONFIGURATION": "profile",
"OUTPUT_DIRECTORY": "build\\windows_vs2019",
"CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_MODULE_PATH=!WORKSPACE!/o3de/install/cmake",
"CMAKE_LY_PROJECTS": "",
"CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_MODULE_PATH=%WORKSPACE%/o3de/install/cmake",
"CMAKE_TARGET": "ALL_BUILD",
"CMAKE_NATIVE_BUILD_ARGS": "/m /nologo"
}
@@ -9,6 +9,13 @@ REM
SETLOCAL EnableDelayedExpansion
REM Jenkins reports MSB8029 when TMP/TEMP is not defined, define a dummy folder
SET TMP=%cd%/temp
SET TEMP=%cd%/temp
IF NOT EXIST %TMP% (
MKDIR temp
)
CALL %~dp0env_windows.cmd
IF NOT EXIST "%OUTPUT_DIRECTORY%" (
@@ -25,18 +32,14 @@ IF ERRORLEVEL 1 (
exit /b 1
)
REM Jenkins reports MSB8029 when TMP/TEMP is not defined, define a dummy folder
SET TMP=%cd%/temp
SET TEMP=%cd%/temp
IF NOT EXIST %TMP% (
MKDIR temp
)
REM Compute half the amount of processors so some jobs can run
SET /a HALF_PROCESSORS = NUMBER_OF_PROCESSORS / 2
SET LAST_CONFIGURE_CMD_FILE=ci_last_configure_cmd.txt
SET CONFIGURE_CMD=cmake %SOURCE_DIRECTORY% %CMAKE_OPTIONS% %EXTRA_CMAKE_OPTIONS% -DLY_3RDPARTY_PATH="%LY_3RDPARTY_PATH%" -DLY_PROJECTS=%CMAKE_LY_PROJECTS%
SET CONFIGURE_CMD=cmake %SOURCE_DIRECTORY% %CMAKE_OPTIONS% %EXTRA_CMAKE_OPTIONS% -DLY_3RDPARTY_PATH="%LY_3RDPARTY_PATH%"
IF NOT "%CMAKE_LY_PROJECTS%"=="" (
SET CONFIGURE_CMD=!CONFIGURE_CMD! -DLY_PROJECTS="%CMAKE_LY_PROJECTS%"
)
IF NOT EXIST CMakeCache.txt (
ECHO [ci_build] First run, generating
SET RUN_CONFIGURE=1
@@ -13,6 +13,11 @@ IF NOT %ERRORLEVEL%==0 (
GOTO :error
)
IF NOT "%COMMAND_CWD%"=="" (
ECHO [ci_build] Changing CWD to %COMMAND_CWD%
CD %COMMAND_CWD%
)
EXIT /b 0
:error
+100 -40
View File
@@ -6,6 +6,7 @@
#
import argparse
from collections import OrderedDict
import fnmatch
import json
import os
@@ -24,15 +25,19 @@ class LicenseScanner:
"""
DEFAULT_CONFIG_FILE = 'scanner_config.json'
DEFAULT_EXCLUDE_FILE = '.gitignore'
DEFAULT_PACKAGE_INFO_FILE = 'PackageInfo.json'
def __init__(self, config_file=None):
self.config_file = config_file
self.config_data = self._load_config()
self.license_regex = self._load_license_regex()
self.file_regex = self._load_file_regex(self.config_data['license_patterns'])
self.package_info = self._load_file_regex(self.config_data['package_patterns'])
self.excluded_directories = self._load_file_regex(self.config_data['excluded_directories'])
def _load_config(self):
"""Load config from the provided file. Sets default file if one is not provided."""
if self.config_file is None:
if not self.config_file:
script_directory = os.path.dirname(os.path.abspath(__file__)) # Default file expected in same dir as script
self.config_file = os.path.join(script_directory, self.DEFAULT_CONFIG_FILE)
@@ -43,45 +48,68 @@ class LicenseScanner:
print('Config file cannot be found')
raise
def _load_license_regex(self):
def _load_file_regex(self, patterns):
"""Returns regex object with case-insensitive matching from the list of filename patterns."""
regex_patterns = []
for pattern in self.config_data['license_patterns']:
for pattern in patterns:
regex_patterns.append(fnmatch.translate(pattern))
if not regex_patterns:
print(f'Warning: No patterns from {patterns} found')
return None
return re.compile('|'.join(regex_patterns), re.IGNORECASE)
def scan(self, path=os.curdir):
"""Scan directory tree for filenames matching license_regex.
def scan(self, paths=os.curdir):
"""Scan directory tree for filenames matching file_regex, package info, and exclusion files.
:param path: Path of the directory to run scanner
:return: Package paths and their corresponding license file contents
:rtype: dict
:param paths: Paths of the directory to run scanner
:return: Package paths and their corresponding file contents
:rtype: Ordered dict
"""
licenses = 0
license_files = {}
files = 0
matching_files = OrderedDict()
excluded_directories = None
for dirpath, dirnames, filenames in os.walk(path):
for file in filenames:
if self.license_regex.match(file):
license_file_content = self._get_license_file_contents(os.path.join(dirpath, file))
rel_dirpath = os.path.relpath(dirpath, path) # Limit path inside scanned directory
license_files[rel_dirpath] = license_file_content
licenses += 1
print(f'License file: {os.path.join(dirpath, file)}')
if not self.package_info:
self.package_info = self.DEFAULT_PACKAGE_INFO_FILE
# Remove directories that should not be scanned
for dir in self.config_data['excluded_directories']:
if dir in dirnames:
dirnames.remove(dir)
print(f'{licenses} license files found.')
return license_files
if not self.excluded_directories:
print(f'No excluded directory in config, looking for {self.DEFAULT_EXCLUDE_FILE} instead')
def _get_license_file_contents(self, filepath):
for path in paths:
for dirpath, dirnames, filenames in os.walk(path, topdown=True):
dirnames.sort(key=str.casefold) # Ensure that results are sorted
for file in filenames:
if self.file_regex.match(file) or self.package_info.match(file):
file_path = os.path.join(dirpath, file)
matching_file_content = self._get_file_contents(file_path)
matching_files[file_path] = matching_file_content
files += 1
print(f'Matching file: {file_path}')
if self.package_info.match(file):
dirnames[:] = [] # Stop scanning subdirectories if package info file found
if self.DEFAULT_EXCLUDE_FILE in file and not self.excluded_directories:
ignore_list = self._get_file_contents(os.path.join(dirpath, file)).splitlines()
ignore_list.append('.git') # .gitignore doesn't usually have .git in its exclusions
excluded_directories = self._load_file_regex(ignore_list)
# Remove directories that should not be scanned
if self.excluded_directories:
excluded_directories = self.excluded_directories
for dir in dirnames:
if excluded_directories.match(dir):
dirnames.remove(dir)
print(f'{files} files found.')
return matching_files
def _get_file_contents(self, filepath):
try:
with open(filepath, encoding='utf8') as f:
return f.read()
except UnicodeDecodeError:
print(f'Unable to read license file: {filepath}')
print(f'Unable to read file: {filepath}')
pass
def create_license_file(self, licenses, filepath='NOTICES.txt'):
@@ -89,18 +117,44 @@ class LicenseScanner:
:param licenses: Dict with package paths and their corresponding license file contents
:param filepath: Path to write the file
"""
package_separator = '------------------------------------'
with open(filepath, 'w', encoding='utf8') as f:
"""
license_separator = '------------------------------------'
with open(filepath, 'w', encoding='utf8') as lf:
for directory, license in licenses.items():
license_output = '\n\n'.join([
f'{package_separator}',
f'Package path: {directory}',
'License:',
f'{license}\n'
])
f.write(license_output)
if not self.package_info.match(os.path.basename(directory)):
license_output = '\n\n'.join([
f'{license_separator}',
f'Package path: {os.path.relpath(directory)}',
'License:',
f'{license}\n'
])
lf.write(license_output)
return None
def create_package_file(self, packages, filepath='SPDX-Licenses.json', get_contents=False):
"""Creates file with all the provided SPDX package info summaries in json.
Optional dirpath parameter will follow the license file path in the package info and return its contents in a dictionary
:param licenses: Dict with package info paths and their corresponding file contents
:param filepath: Path to write the file
:param dirpath: Root path for packages
:rtype: Ordered dict
"""
licenses = OrderedDict()
package_json = []
with open(filepath, 'w', encoding='utf8') as pf:
for directory, package in packages.items():
if self.package_info.match(os.path.basename(directory)):
package_obj = json.loads(package)
package_json.append(package_obj)
if get_contents:
license_path = os.path.join(os.path.dirname(directory), pathlib.Path(package_obj['LicenseFile']))
licenses[license_path] = self._get_file_contents(license_path)
else:
licenses[directory] = package
pf.write(json.dumps(package_json, indent=4))
return licenses
def parse_args():
@@ -108,7 +162,8 @@ def parse_args():
description='Script to run LicenseScanner and generate license file')
parser.add_argument('--config-file', '-c', type=pathlib.Path, help='Config file for LicenseScanner')
parser.add_argument('--license-file-path', '-l', type=pathlib.Path, help='Create license file in the provided path')
parser.add_argument('--scan-path', '-s', default=os.curdir, type=pathlib.Path, help='Path to scan')
parser.add_argument('--package-file-path', '-p', type=pathlib.Path, help='Create package summary file in the provided path')
parser.add_argument('--scan-path', '-s', default=os.curdir, type=pathlib.Path, nargs='+', help='Path to scan, multiple space separated paths can be used')
return parser.parse_args()
@@ -116,10 +171,15 @@ def main():
try:
args = parse_args()
ls = LicenseScanner(args.config_file)
licenses = ls.scan(args.scan_path)
scanned_path_data = ls.scan(args.scan_path)
if args.license_file_path:
ls.create_license_file(licenses, args.license_file_path)
ls.create_license_file(scanned_path_data, args.license_file_path)
if args.package_file_path:
ls.create_package_file(scanned_path_data, args.package_file_path)
if args.license_file_path and args.package_file_path:
license_files = ls.create_package_file(scanned_path_data, args.package_file_path, True)
ls.create_license_file(license_files, args.license_file_path)
except FileNotFoundError as e:
print(f'Type: {type(e).__name__}, Error: {e}')
return 1
@@ -8,5 +8,8 @@
"license_patterns": [
"LICENSE*",
"COPYING*"
],
"package_patterns": [
"PackageInfo.json"
]
}