Merge branch 'development' into Atom/santorac/FixSceneSrgTime

This commit is contained in:
santorac
2021-11-08 10:22:03 -08:00
160 changed files with 2551 additions and 2429 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/**
@@ -46,7 +46,7 @@ namespace AutomatedTesting
void AutomatedTestingSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
AZ_UNUSED(required);
required.push_back(AZ_CRC_CE("MultiplayerService"));
}
void AutomatedTestingSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
@@ -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
@@ -13,10 +13,10 @@ import zipfile
import pytest
import ly_test_tools.environment.file_system as file_system
from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots
from ly_test_tools.benchmark.data_aggregator import BenchmarkDataAggregator
import editor_python_test_tools.hydra_test_utils as hydra
from .atom_utils.atom_component_helper import compare_screenshot_similarity, ImageComparisonTestFailure
logger = logging.getLogger(__name__)
DEFAULT_SUBFOLDER_PATH = 'user/PythonTests/Automated/Screenshots'
@@ -69,7 +69,7 @@ def create_screenshots_archive(screenshot_path):
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ["windows_editor"])
@pytest.mark.parametrize("level", ["auto_test"])
@pytest.mark.parametrize("level", ["Base"])
class TestAllComponentsIndepthTests(object):
@pytest.mark.parametrize("screenshot_name", ["AtomBasicLevelSetup.ppm"])
@@ -91,12 +91,7 @@ class TestAllComponentsIndepthTests(object):
"Viewport is set to the expected size: True",
"Exited game mode"
]
unexpected_lines = [
"Trace::Assert",
"Trace::Error",
"Traceback (most recent call last):",
"Screenshot failed"
]
unexpected_lines = ["Traceback (most recent call last):"]
hydra.launch_and_validate_results(
request,
@@ -111,10 +106,12 @@ class TestAllComponentsIndepthTests(object):
null_renderer=False,
)
for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images):
compare_screenshots(test_screenshot, golden_screenshot)
create_screenshots_archive(screenshot_directory)
similarity_threshold = 0.99
for test_screenshot, golden_image in zip(test_screenshots, golden_images):
screenshot_comparison_result = compare_screenshot_similarity(
test_screenshot, golden_image, similarity_threshold, True, screenshot_directory)
if screenshot_comparison_result != "Screenshots match":
raise Exception(f"Screenshot test failed: {screenshot_comparison_result}")
@pytest.mark.test_case_id("C34525095")
def test_LightComponent_ScreenshotMatchesGoldenImage(
@@ -149,12 +146,7 @@ class TestAllComponentsIndepthTests(object):
golden_images.append(golden_image_path)
expected_lines = ["spot_light Controller|Configuration|Shadows|Shadowmap size: SUCCESS"]
unexpected_lines = [
"Trace::Assert",
"Trace::Error",
"Traceback (most recent call last):",
"Screenshot failed",
]
unexpected_lines = ["Traceback (most recent call last):"]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
@@ -168,10 +160,12 @@ class TestAllComponentsIndepthTests(object):
null_renderer=False,
)
for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images):
compare_screenshots(test_screenshot, golden_screenshot)
create_screenshots_archive(screenshot_directory)
similarity_threshold = 0.99
for test_screenshot, golden_image in zip(test_screenshots, golden_images):
screenshot_comparison_result = compare_screenshot_similarity(
test_screenshot, golden_image, similarity_threshold, True, screenshot_directory)
if screenshot_comparison_result != "Screenshots match":
raise ImageComparisonTestFailure(f"Screenshot test failed: {screenshot_comparison_result}")
@pytest.mark.parametrize('rhi', ['dx12', 'vulkan'])
@@ -219,7 +213,6 @@ class TestPerformanceBenchmarkSuite(object):
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_generic'])
@pytest.mark.system
class TestMaterialEditor(object):
@pytest.mark.parametrize("cfg_args,expected_lines", [
@@ -1,83 +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("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("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):"
]
@@ -1,5 +1,6 @@
"""
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.
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
@@ -8,12 +9,19 @@ import datetime
import os
import zipfile
from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots
class ImageComparisonTestFailure(Exception):
"""Custom test failure for failed image comparisons."""
pass
def create_screenshots_archive(screenshot_path):
"""
Creates a new zip file archive at archive_path containing all files listed within archive_path.
:param screenshot_path: location containing the files to archive, the zip archive file will also be saved here.
:return: None, but creates a new zip file archive inside path containing all of the files inside archive_path.
:return: path to the created .zip file archive.
"""
files_to_archive = []
@@ -27,14 +35,16 @@ def create_screenshots_archive(screenshot_path):
# Setup variables for naming the zip archive file.
timestamp = datetime.datetime.now().timestamp()
formatted_timestamp = datetime.datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d_%H-%M-%S")
screenshots_file = os.path.join(screenshot_path, f'screenshots_{formatted_timestamp}.zip')
screenshots_zip_file = os.path.join(screenshot_path, f'screenshots_{formatted_timestamp}.zip')
# Write all of the valid .png and .ppm files to the archive file.
with zipfile.ZipFile(screenshots_file, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zip_archive:
with zipfile.ZipFile(screenshots_zip_file, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zip_archive:
for file_path in files_to_archive:
file_name = os.path.basename(file_path)
zip_archive.write(file_path, file_name)
return screenshots_zip_file
def golden_images_directory():
"""
@@ -53,6 +63,36 @@ def golden_images_directory():
return golden_images_dir
def compare_screenshot_similarity(
test_screenshot, golden_image, similarity_threshold, create_zip_archive=False, screenshot_directory=""):
"""
Compares the similarity between a test screenshot and a golden image.
It returns a "Screenshots match" string if the comparison mean value is higher than the similarity threshold.
Otherwise, it returns an error string.
:param test_screenshot: path to the test screenshot to compare.
:param golden_image: path to the golden image to compare.
:param similarity_threshold: value for the comparison mean value to be asserted against.
:param create_zip_archive: toggle to create a zip archive containing the screenshots if the assert check fails.
:param screenshot_directory: directory containing screenshots to create zip archive from.
:return: Error string if compared mean value < similarity threshold or screenshot_directory is missing for .zip,
otherwise it returns a "Screenshots match" string.
"""
result = "Screenshots match"
if create_zip_archive and not screenshot_directory:
result = 'You must specify a screenshot_directory in order to create a zip archive.\n'
mean_similarity = compare_screenshots(test_screenshot, golden_image)
if not mean_similarity > similarity_threshold:
if create_zip_archive:
create_screenshots_archive(screenshot_directory)
result = (
f"When comparing the test_screenshot: '{test_screenshot}' "
f"to golden_image: '{golden_image}' the mean similarity of '{mean_similarity}' "
f"was lower than the similarity threshold of '{similarity_threshold}'. ")
return result
def create_basic_atom_level(level_name):
"""
Creates a new level inside the Editor matching level_name & adds the following:
@@ -76,29 +116,11 @@ def create_basic_atom_level(level_name):
helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper")
# Create a new level.
new_level_name = level_name
heightmap_resolution = 512
heightmap_meters_per_pixel = 1
terrain_texture_resolution = 412
use_terrain = False
# Return codes are ECreateLevelResult defined in CryEdit.h
return_code = general.create_level_no_prompt(
new_level_name, heightmap_resolution, heightmap_meters_per_pixel, terrain_texture_resolution, use_terrain)
if return_code == 1:
general.log(f"{new_level_name} level already exists")
elif return_code == 2:
general.log("Failed to create directory")
elif return_code == 3:
general.log("Directory length is too long")
elif return_code != 0:
general.log("Unknown error, failed to create level")
else:
general.log(f"{new_level_name} level created successfully")
# Enable idle and update viewport.
# Wait for Editor idle loop before executing Python hydra scripts.
general.idle_enable(True)
# Basic setup for opened level.
helper.open_level(level_name="Base")
general.idle_wait(1.0)
general.update_viewport()
general.idle_wait(0.5) # half a second is more than enough for updating the viewport.
@@ -165,24 +187,25 @@ def create_basic_atom_level(level_name):
components=["Material"],
parent_id=default_level.id)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", ground_plane.id, 32.0)
ground_plane_material_asset_path = os.path.join(
"Materials", "Presets", "PBR", "metal_chrome.azmaterial")
ground_plane_material_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False)
ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset_value)
# Work around to add the correct Atom Mesh component
# Work around to add the correct Atom Mesh component and asset.
mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId
ground_plane.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", ground_plane.id, [mesh_type_id]
).GetValue()[0]
)
ground_plane_mesh_asset_path = os.path.join("Models", "plane.azmodel")
ground_plane_mesh_asset_path = os.path.join("TestData", "Objects", "plane.azmodel")
ground_plane_mesh_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_mesh_asset_path, math.Uuid(), False)
ground_plane.get_set_test(1, "Controller|Configuration|Mesh Asset", ground_plane_mesh_asset_value)
# Add Atom Material component and asset.
ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial")
ground_plane_material_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False)
ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset_value)
# Create directional_light entity and set the properties
directional_light = hydra.Entity("directional_light")
directional_light.create_entity(
@@ -199,12 +222,8 @@ def create_basic_atom_level(level_name):
entity_position=math.Vector3(0.0, 0.0, 1.0),
components=["Material"],
parent_id=default_level.id)
sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial")
sphere_material_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False)
sphere_entity.get_set_test(0, "Default Material|Material Asset", sphere_material_asset_value)
# Work around to add the correct Atom Mesh component
# Work around to add the correct Atom Mesh component and asset.
sphere_entity.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", sphere_entity.id, [mesh_type_id]
@@ -215,6 +234,12 @@ def create_basic_atom_level(level_name):
bus.Broadcast, "GetAssetIdByPath", sphere_mesh_asset_path, math.Uuid(), False)
sphere_entity.get_set_test(1, "Controller|Configuration|Mesh Asset", sphere_mesh_asset_value)
# Add Atom Material component and asset.
sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial")
sphere_material_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False)
sphere_entity.get_set_test(0, "Default Material|Material Asset", sphere_material_asset_value)
# Create camera component and set the properties
camera_entity = hydra.Entity("camera")
camera_entity.create_entity(
@@ -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,12 +216,14 @@ 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.
"""
properties = {
'name': 'HDR Color Grading',
'requires': [AtomComponentProperties.postfx_layer()],
'Enable HDR color grading': 'Controller|Configuration|Enable HDR color grading',
}
return properties[property]
@@ -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)
@@ -0,0 +1,192 @@
"""
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")
hdr_color_grading_creation = (
"HDR Color Grading Entity successfully created",
"HDR Color Grading Entity failed to be created")
hdr_color_grading_component = (
"Entity has an HDR Color Grading component",
"Entity failed to find HDR Color Grading component")
hdr_color_grading_disabled = (
"HDR Color Grading component disabled",
"HDR Color Grading component was not disabled")
postfx_layer_component = (
"Entity has a PostFX Layer component",
"Entity did not have an PostFX Layer component")
hdr_color_grading_enabled = (
"HDR Color Grading component enabled",
"HDR Color Grading component was not enabled")
enable_hdr_color_grading_parameter_enabled = (
"Enable HDR Color Grading parameter enabled",
"Enable HDR Color Grading 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_HDRColorGrading_AddedToEntity():
"""
Summary:
Tests the HDR Color Grading 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 HDR Color Grading entity with no components.
2) Add HDR Color Grading component to HDR Color Grading entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Verify HDR Color Grading component not enabled.
6) Add PostFX Layer component since it is required by the HDR Color Grading component.
7) Verify HDR Color Grading component is enabled.
8) Enable the "Enable HDR Color Grading" parameter.
9) Enter/Exit game mode.
10) Test IsHidden.
11) Test IsVisible.
12) Delete HDR Color Grading 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 HDR Color Grading entity with no components.
hdr_color_grading_entity = EditorEntity.create_editor_entity(AtomComponentProperties.hdr_color_grading())
Report.critical_result(Tests.hdr_color_grading_creation, hdr_color_grading_entity.exists())
# 2. Add HDR Color Grading component to HDR Color Grading entity.
hdr_color_grading_component = hdr_color_grading_entity.add_component(
AtomComponentProperties.hdr_color_grading())
Report.critical_result(
Tests.hdr_color_grading_component,
hdr_color_grading_entity.has_component(AtomComponentProperties.hdr_color_grading()))
# 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 hdr_color_grading_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, hdr_color_grading_entity.exists())
# 5. Verify HDR Color Grading component not enabled.
Report.result(Tests.hdr_color_grading_disabled, not hdr_color_grading_component.is_enabled())
# 6. Add PostFX Layer component since it is required by the HDR Color Grading component.
hdr_color_grading_entity.add_component(AtomComponentProperties.postfx_layer())
Report.result(
Tests.postfx_layer_component,
hdr_color_grading_entity.has_component(AtomComponentProperties.postfx_layer()))
# 7. Verify HDR Color Grading component is enabled.
Report.result(Tests.hdr_color_grading_enabled, hdr_color_grading_component.is_enabled())
# 8. Enable the "Enable HDR Color Grading" parameter.
hdr_color_grading_component.set_component_property_value(
AtomComponentProperties.hdr_color_grading('Enable HDR color grading'), True)
Report.result(Tests.enable_hdr_color_grading_parameter_enabled,
hdr_color_grading_component.get_component_property_value(
AtomComponentProperties.hdr_color_grading('Enable HDR color grading')) 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.
hdr_color_grading_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, hdr_color_grading_entity.is_hidden() is True)
# 11. Test IsVisible.
hdr_color_grading_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, hdr_color_grading_entity.is_visible() is True)
# 12. Delete HDR Color Grading entity.
hdr_color_grading_entity.delete()
Report.result(Tests.entity_deleted, not hdr_color_grading_entity.exists())
# 13. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, hdr_color_grading_entity.exists())
# 14. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not hdr_color_grading_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_HDRColorGrading_AddedToEntity)
@@ -0,0 +1,159 @@
"""
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")
occlusion_culling_plane_entity_creation = (
"Occlusion Culling Plane Entity successfully created",
"Occlusion Culling Plane Entity failed to be created")
occlusion_culling_plane_component_added = (
"Entity has a Occlusion Culling Plane component",
"Entity failed to find Occlusion Culling Plane component")
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_OcclusionCullingPlane_AddedToEntity():
"""
Summary:
Tests the occlusion culling plane 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 a Occlusion Culling Plane entity with no components.
2) Add a Occlusion Culling Plane component to Occlusion Culling Plane entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Enter/Exit game mode.
6) Test IsHidden.
7) Test IsVisible.
8) Delete Occlusion Culling Plane entity.
9) UNDO deletion.
10) REDO deletion.
11) 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 a occlusion culling plane entity with no components.
occlusion_culling_plane_entity = EditorEntity.create_editor_entity(
AtomComponentProperties.occlusion_culling_plane())
Report.critical_result(Tests.occlusion_culling_plane_entity_creation,
occlusion_culling_plane_entity.exists())
# 2. Add a occlusion culling plane component to occlusion culling plane entity.
occlusion_culling_plane_component = occlusion_culling_plane_entity.add_component(
AtomComponentProperties.occlusion_culling_plane())
Report.critical_result(
Tests.occlusion_culling_plane_component_added,
occlusion_culling_plane_entity.has_component(AtomComponentProperties.occlusion_culling_plane()))
# 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 occlusion_culling_plane_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, occlusion_culling_plane_entity.exists())
# 5. 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)
# 6. Test IsHidden.
occlusion_culling_plane_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, occlusion_culling_plane_entity.is_hidden() is True)
# 7. Test IsVisible.
occlusion_culling_plane_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, occlusion_culling_plane_entity.is_visible() is True)
# 8. Delete occlusion_culling_plane entity.
occlusion_culling_plane_entity.delete()
Report.result(Tests.entity_deleted, not occlusion_culling_plane_entity.exists())
# 9. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, occlusion_culling_plane_entity.exists())
# 10. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not occlusion_culling_plane_entity.exists())
# 11. Look for errors or 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_OcclusionCullingPlane_AddedToEntity)
@@ -6,18 +6,6 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import sys
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.camera
import azlmbr.entity as entity
import azlmbr.legacy.general as general
import azlmbr.math as math
import azlmbr.paths
import azlmbr.editor as editor
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.editor_test_helper import EditorTestHelper
@@ -45,9 +33,18 @@ def run():
11. Adds a "camera" entity to "default_level" & adds a Camera component with 80 degree FOV and Transform values:
Translate - x:5.5m, y:-12.0m, z:9.0m
Rotate - x:-27.0, y:-12.0, z:25.0
12. Finally enters game mode, takes a screenshot, exits game mode, & saves the level.
12. Finally enters game mode, takes a screenshot, & exits game mode.
:return: None
"""
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.camera as camera
import azlmbr.entity as entity
import azlmbr.legacy.general as general
import azlmbr.math as math
import azlmbr.paths
import azlmbr.editor as editor
def initial_viewport_setup(screen_width, screen_height):
general.set_viewport_size(screen_width, screen_height)
general.update_viewport()
@@ -84,33 +81,11 @@ def run():
general.run_console("r_displayInfo=0")
general.idle_wait(1.0)
return True
# Wait for Editor idle loop before executing Python hydra scripts.
general.idle_enable(True)
# Open the auto_test level.
new_level_name = "auto_test" # Specified in class TestAllComponentsIndepthTests()
heightmap_resolution = 512
heightmap_meters_per_pixel = 1
terrain_texture_resolution = 412
use_terrain = False
# Return codes are ECreateLevelResult defined in CryEdit.h
return_code = general.create_level_no_prompt(
new_level_name, heightmap_resolution, heightmap_meters_per_pixel, terrain_texture_resolution, use_terrain)
if return_code == 1:
general.log(f"{new_level_name} level already exists")
elif return_code == 2:
general.log("Failed to create directory")
elif return_code == 3:
general.log("Directory length is too long")
elif return_code != 0:
general.log("Unknown error, failed to create level")
else:
general.log(f"{new_level_name} level created successfully")
# Basic setup for newly created level.
# Basic setup for opened level.
helper.open_level(level_name="Base")
after_level_load()
initial_viewport_setup(SCREEN_WIDTH, SCREEN_HEIGHT)
@@ -147,22 +122,25 @@ def run():
parent_id=default_level.id
)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", ground_plane.id, 32.0)
ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial")
ground_plane_material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False)
ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset)
# Work around to add the correct Atom Mesh component
# Work around to add the correct Atom Mesh component and asset.
mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId
ground_plane.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", ground_plane.id, [mesh_type_id]
).GetValue()[0]
)
ground_plane_mesh_asset_path = os.path.join("Objects", "plane.azmodel")
ground_plane_mesh_asset_path = os.path.join("TestData", "Objects", "plane.azmodel")
ground_plane_mesh_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_mesh_asset_path, math.Uuid(), False)
hydra.get_set_test(ground_plane, 1, "Controller|Configuration|Mesh Asset", ground_plane_mesh_asset)
# Add Atom Material component and asset.
ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial")
ground_plane_material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False)
ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset)
# Create directional_light entity and set the properties
directional_light = hydra.Entity("directional_light")
directional_light.create_entity(
@@ -180,11 +158,8 @@ def run():
components=["Material"],
parent_id=default_level.id
)
sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial")
sphere_material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False)
sphere.get_set_test(0, "Default Material|Material Asset", sphere_material_asset)
# Work around to add the correct Atom Mesh component
# Work around to add the correct Atom Mesh component and asset.
sphere.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", sphere.id, [mesh_type_id]
@@ -195,6 +170,12 @@ def run():
bus.Broadcast, "GetAssetIdByPath", sphere_mesh_asset_path, math.Uuid(), False)
hydra.get_set_test(sphere, 1, "Controller|Configuration|Mesh Asset", sphere_mesh_asset)
# Add Atom Material component and asset.
sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial")
sphere_material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False)
sphere.get_set_test(0, "Default Material|Material Asset", sphere_material_asset)
# Create camera component and set the properties
camera_entity = hydra.Entity("camera")
position = math.Vector3(5.5, -12.0, 9.0)
@@ -204,10 +185,9 @@ def run():
)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", camera_entity.id, rotation)
camera_entity.get_set_test(0, "Controller|Configuration|Field of view", 60.0)
azlmbr.camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id)
camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id)
# Save level, enter game mode, take screenshot, & exit game mode.
general.save_level()
# Enter game mode, take screenshot, & exit game mode.
general.idle_wait(0.5)
general.enter_game_mode()
general.idle_wait(1.0)
@@ -22,7 +22,7 @@ from editor_python_test_tools.editor_test_helper import EditorTestHelper
helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper")
LEVEL_NAME = "auto_test"
LEVEL_NAME = "Base"
LIGHT_COMPONENT = "Light"
LIGHT_TYPE_PROPERTY = 'Controller|Configuration|Light type'
DEGREE_RADIAN_FACTOR = 0.0174533
@@ -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)
@@ -329,7 +329,7 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
# or an expected behavior has changed. Processing bootstrap.cfg sometimes but not other times should not
# cause a failure in this test.
num_processed_assets = asset_processor_utils.get_num_processed_assets(output)
assert num_processed_assets >= 8, f'Wrong number of successfully processed assets found in output: '\
assert num_processed_assets >= 6, f'Wrong number of successfully processed assets found in output: '\
'{num_processed_assets}'
missing_assets, _ = asset_processor.compare_assets_with_cache()
@@ -585,20 +585,18 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
3. Verify that logs exist for both AP Batch & AP GUI
"""
asset_processor.create_temp_asset_root()
asset_processor.create_temp_log_root()
LOG_PATH = {
"batch_log": workspace.paths.ap_batch_log(),
"gui_log": workspace.paths.ap_gui_log(),
"job_logs": workspace.paths.ap_job_logs(),
"gui_log": workspace.paths.ap_gui_log()
}
class LogTimes:
batch_log_start_time = 0
gui_log_start_time = 0
job_logs_start_time = 0
batch_log_final_time = 0
gui_log_final_time = 0
job_logs_final_time = 0
@staticmethod
def Report():
@@ -607,12 +605,10 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
Original Times:
Batch: {LogTimes.batch_log_start_time}
GUI: {LogTimes.gui_log_start_time}
JobLogs:{LogTimes.job_logs_start_time}
Post-Run Times:
Batch: {LogTimes.batch_log_final_time}
GUI: {LogTimes.gui_log_final_time}
JobLogs:{LogTimes.job_logs_final_time}
"""
)
@@ -629,23 +625,19 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
LogTimes.Report()
def check_existence(name, path):
assert os.path.exists(path), f"{name} could not be located after running the AP."
assert os.path.exists(path), f"{name} could not be located {path} after running the AP."
# Check if log files previously exist and grab their modification times
update_times("_start_time")
# Run the Batch process
assert asset_processor.batch_process(), "Batch process failed to successfully terminate"
assert asset_processor.batch_process(create_temp_log=False), "Batch process failed to successfully terminate"
asset_processor.gui_process(quitonidle=True)
asset_processor.gui_process(quitonidle=True, create_temp_log=False)
# Check that the Logs directory exists (C1564055)
check_existence("Logs Directory", workspace.paths.ap_log_dir())
# Check that the logs and JobLogs directory have updated modified times (C1564056)
for key in LOG_PATH.keys():
check_existence(key, LOG_PATH[key])
update_times("_final_time")
for key in LOG_PATH.keys():
@@ -708,6 +700,7 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
@pytest.mark.BAT
@pytest.mark.assetpipeline
@pytest.mark.skip(reason="need to change assets from .slice files to an asset type that can have nested dependencies")
def test_validateNestedPreloadDependency_Found(self, asset_processor, ap_setup_fixture, workspace):
"""
Tests processing of a nested circular dependency and verifies that Asset Processor will return an error
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7932fada1523fad4eb6ad5c13111bb4c00d6b0584ec8641060bf25f306b17e69
size 84632
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bf94b548eccb78432db65077124cadf20de975919b181d712fde081f59edd353
size 38848
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1675471085483e0bd252a5bdd4db1b365c66f16ac32a6e6dd70bb1c23df83fa5
size 24160
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1675471085483e0bd252a5bdd4db1b365c66f16ac32a6e6dd70bb1c23df83fa5
size 24160
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:21df6ab62f2572daa6d0710ad6819a728ee751a62170903a6773563d614aa51f
size 15191
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:21df6ab62f2572daa6d0710ad6819a728ee751a62170903a6773563d614aa51f
size 15191
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f0b4750147acbcc6229a1043ed47ee48e7703a5241f27fc72976e95741144369
size 2372
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a036c3763b96079dde8505ad6995f8b717a777c78d7a434ac8de6f1ccb5d8dd1
size 4327
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cf55a4372d82d70d8cdcc95468367cd4fad7649d3fb99c4d85049977b506885c
size 13429
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1821d000b583821fd36ec73f91073d51ae90762225a34a81a74771c36236b5e1
size 12963
-1
View File
@@ -548,7 +548,6 @@ public:
{ "BatchMode", m_bConsoleMode },
{ "NullRenderer", m_bNullRenderer },
{ "devmode", m_bDeveloperMode },
{ "VTUNE", dummy },
{ "runpython", m_bRunPythonScript },
{ "runpythontest", m_bRunPythonTestScript },
{ "version", m_bShowVersionInfo },
-16
View File
@@ -60,15 +60,6 @@
#include <LmbrCentral/Audio/AudioSystemComponentBus.h>
#include <LmbrCentral/Rendering/EditorLightComponentBus.h> // for LmbrCentral::EditorLightComponentRequestBus
//#define PROFILE_LOADING_WITH_VTUNE
// profilers api.
//#include "pure.h"
#ifdef PROFILE_LOADING_WITH_VTUNE
#include "C:\Program Files\Intel\Vtune\Analyzer\Include\VTuneApi.h"
#pragma comment(lib,"C:\\Program Files\\Intel\\Vtune\\Analyzer\\Lib\\VTuneApi.lib")
#endif
static const char* kAutoBackupFolder = "_autobackup";
static const char* kHoldFolder = "$tmp_hold"; // conform to the ignored file types $tmp[0-9]*_ regex
static const char* kSaveBackupFolder = "_savebackup";
@@ -408,9 +399,6 @@ void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename)
int t0 = GetTickCount();
#ifdef PROFILE_LOADING_WITH_VTUNE
VTResume();
#endif
// Load level-specific audio data.
AZStd::string levelFileName{ fileName.toUtf8().constData() };
AZStd::to_lower(levelFileName.begin(), levelFileName.end());
@@ -484,10 +472,6 @@ void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename)
CSurfaceTypeValidator().Validate();
#ifdef PROFILE_LOADING_WITH_VTUNE
VTPause();
#endif
LogLoadTime(GetTickCount() - t0);
// Loaded with success, remove event from log file
GetIEditor()->GetSettingsManager()->UnregisterEvent(loadEvent);
@@ -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);
}
}
@@ -54,6 +54,7 @@ namespace AzFramework
RECT m_windowRectToRestoreOnFullScreenExit; //!< The position and size of the window to restore when exiting full screen.
UINT m_windowStyleToRestoreOnFullScreenExit; //!< The style(s) of the window to restore when exiting full screen.
bool m_isInBorderlessWindowFullScreenState = false; //!< Was a borderless window used to enter full screen state?
bool m_shouldEnterFullScreenStateOnActivate = false; //!< Should we enter full screen state when the window is activated?
using GetDpiForWindowType = UINT(HWND hwnd);
GetDpiForWindowType* m_getDpiFunction = nullptr;
@@ -249,6 +250,28 @@ namespace AzFramework
AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputCodeUnitUTF16Event, codeUnitUTF16);
break;
}
case WM_ACTIVATE:
{
// Alt-tabbing out of the app while it is in a full screen state does not
// work unless we explicitly exit the full screen state upon deactivation,
// in which case we want to enter full screen state again upon activation.
const bool windowIsNowInactive = (LOWORD(wParam) == WA_INACTIVE);
const bool windowFullScreenState = nativeWindowImpl->GetFullScreenState();
if (windowIsNowInactive &&
windowFullScreenState)
{
nativeWindowImpl->m_shouldEnterFullScreenStateOnActivate = true;
nativeWindowImpl->SetFullScreenState(false);
}
else if (!windowIsNowInactive &&
!windowFullScreenState &&
nativeWindowImpl->m_shouldEnterFullScreenStateOnActivate)
{
nativeWindowImpl->m_shouldEnterFullScreenStateOnActivate = false;
nativeWindowImpl->SetFullScreenState(true);
}
break;
}
case WM_SYSKEYDOWN:
{
// Handle ALT+ENTER to toggle full screen unless exclsuive full screen
@@ -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;
@@ -657,13 +657,18 @@ bool SpinBoxWatcher::handleMouseDragStepping(QAbstractSpinBox* spinBox, QEvent*
QPoint screenPos = mouseEvent->screenPos().toPoint();
const int xPos = screenPos.x();
int newXPos = xPos;
// cursor bounces on the left and right side of the screen
// looks like buggy behaviour so mouse cursor is wrapped
// around to the other side of the screen.
if (xPos >= screenRect.right())
{
newXPos = screenRect.right() - 1;
// wraps mouse cursor around to the left side of the screen
newXPos = screenRect.left() + 1;
}
else if (xPos <= screenRect.left())
{
newXPos = screenRect.left() + 1;
// wraps mouse cursor around to the right side of the screen
newXPos = screenRect.right() - 1;
}
if (newXPos != xPos)
@@ -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
+2 -21
View File
@@ -739,24 +739,6 @@ public:
#undef GetUserName
#endif
struct IProfilingSystem
{
// <interfuscator:shuffle>
virtual ~IProfilingSystem() {}
//////////////////////////////////////////////////////////////////////////
// VTune Profiling interface.
// Summary:
// Resumes vtune data collection.
virtual void VTuneResume() = 0;
// Summary:
// Pauses vtune data collection.
virtual void VTunePause() = 0;
//////////////////////////////////////////////////////////////////////////
// </interfuscator:shuffle>
};
////////////////////////////////////////////////////////////////////////////////////////////////
// Description:
@@ -851,7 +833,6 @@ struct ISystem
virtual IMovieSystem* GetIMovieSystem() = 0;
virtual ::IConsole* GetIConsole() = 0;
virtual IRemoteConsole* GetIRemoteConsole() = 0;
virtual IProfilingSystem* GetIProfilingSystem() = 0;
virtual ISystemEventDispatcher* GetISystemEventDispatcher() = 0;
virtual ITimer* GetITimer() = 0;
@@ -1121,8 +1102,8 @@ inline ISystem* GetISystem()
// Description:
// This function must be called once by each module at the beginning, to setup global pointers.
extern "C" AZ_DLL_EXPORT void ModuleInitISystem(ISystem* pSystem, const char* moduleName);
extern "C" AZ_DLL_EXPORT void ModuleShutdownISystem(ISystem* pSystem);
void ModuleInitISystem(ISystem* pSystem, const char* moduleName);
void ModuleShutdownISystem(ISystem* pSystem);
extern "C" AZ_DLL_EXPORT void InjectEnvironment(void* env);
extern "C" AZ_DLL_EXPORT void DetachEnvironment();
@@ -76,8 +76,6 @@ public:
::IConsole * ());
MOCK_METHOD0(GetIRemoteConsole,
IRemoteConsole * ());
MOCK_METHOD0(GetIProfilingSystem,
IProfilingSystem * ());
MOCK_METHOD0(GetISystemEventDispatcher,
ISystemEventDispatcher * ());
MOCK_METHOD0(GetITimer,
-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 -2
View File
@@ -74,7 +74,7 @@ void InitCRTHandlers() {}
//////////////////////////////////////////////////////////////////////////
// This is an entry to DLL initialization function that must be called for each loaded module
//////////////////////////////////////////////////////////////////////////
extern "C" AZ_DLL_EXPORT void ModuleInitISystem(ISystem* pSystem, [[maybe_unused]] const char* moduleName)
void ModuleInitISystem(ISystem* pSystem, [[maybe_unused]] const char* moduleName)
{
if (gEnv) // Already registered.
{
@@ -96,7 +96,7 @@ extern "C" AZ_DLL_EXPORT void ModuleInitISystem(ISystem* pSystem, [[maybe_unused
} // if pSystem
}
extern "C" AZ_DLL_EXPORT void ModuleShutdownISystem([[maybe_unused]] ISystem* pSystem)
void ModuleShutdownISystem([[maybe_unused]] ISystem* pSystem)
{
// Unregister with AZ environment.
AZ::Environment::Detach();
-58
View File
@@ -143,9 +143,6 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
// To enable profiling with vtune (https://software.intel.com/en-us/intel-vtune-amplifier-xe), make sure the line below is not commented out
//#define PROFILE_WITH_VTUNE
#include <process.h>
#include <malloc.h>
#endif
@@ -154,10 +151,6 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
#include <AzFramework/IO/LocalFileIO.h>
// profilers api.
VTuneFunction VTResume = NULL;
VTuneFunction VTPause = NULL;
// Define global cvars.
SSystemCVars g_cvars;
@@ -516,8 +509,6 @@ void CSystem::ShutDown()
ShutdownFileSystem();
ShutdownModuleLibraries();
EBUS_EVENT(CrySystemEventBus, OnCrySystemPostShutdown);
}
@@ -697,31 +688,6 @@ bool CSystem::UpdatePreTickBus(int updateFlags, int nPauseMode)
m_bPaused = false;
}
#ifdef PROFILE_WITH_VTUNE
if (m_bInDevMode)
{
if (VTPause != NULL && VTResume != NULL)
{
static bool bVtunePaused = true;
const AzFramework::InputChannel* inputChannelScrollLock = AzFramework::InputChannelRequests::FindInputChannel(AzFramework::InputDeviceKeyboard::Key::WindowsSystemScrollLock);
const bool bPaused = (inputChannelScrollLock ? inputChannelScrollLock->IsActive() : false);
{
if (bVtunePaused && !bPaused)
{
GetIProfilingSystem()->VTuneResume();
}
if (!bVtunePaused && bPaused)
{
GetIProfilingSystem()->VTunePause();
}
bVtunePaused = bPaused;
}
}
}
#endif //PROFILE_WITH_VTUNE
#ifndef EXCLUDE_UPDATE_ON_CONSOLE
if (m_bIgnoreUpdates)
{
@@ -1255,30 +1221,6 @@ CPNoise3* CSystem::GetNoiseGen()
return &m_pNoiseGen;
}
//////////////////////////////////////////////////////////////////////////
void CProfilingSystem::VTuneResume()
{
#ifdef PROFILE_WITH_VTUNE
if (VTResume)
{
CryLogAlways("VTune Resume");
VTResume();
}
#endif
}
//////////////////////////////////////////////////////////////////////////
void CProfilingSystem::VTunePause()
{
#ifdef PROFILE_WITH_VTUNE
if (VTPause)
{
VTPause();
CryLogAlways("VTune Pause");
}
#endif
}
//////////////////////////////////////////////////////////////////////
void CSystem::OnLanguageCVarChanged(ICVar* language)
{
-37
View File
@@ -105,10 +105,6 @@ struct IDataProbe;
#define PHSYICS_OBJECT_ENTITY 0
using VTuneFunction = void (__cdecl *)(void);
extern VTuneFunction VTResume;
extern VTuneFunction VTPause;
#define MAX_STREAMING_POOL_INDEX 6
#define MAX_THREAD_POOL_INDEX 6
@@ -139,7 +135,6 @@ struct SSystemCVars
int sys_ai;
int sys_entitysystem;
int sys_trackview;
int sys_vtune;
float sys_update_profile_time;
int sys_limit_phys_thread_count;
int sys_MaxFPS;
@@ -169,21 +164,6 @@ extern SSystemCVars g_cvars;
class CSystem;
struct CProfilingSystem
: public IProfilingSystem
{
//////////////////////////////////////////////////////////////////////////
// VTune Profiling interface.
// Summary:
// Resumes vtune data collection.
void VTuneResume() override;
// Summary:
// Pauses vtune data collection.
void VTunePause() override;
//////////////////////////////////////////////////////////////////////////
};
class AssetSystem;
/*
@@ -262,7 +242,6 @@ public:
IViewSystem* GetIViewSystem() override;
ILevelSystem* GetILevelSystem() override;
ISystemEventDispatcher* GetISystemEventDispatcher() override { return m_pSystemEventDispatcher; }
IProfilingSystem* GetIProfilingSystem() override { return &m_ProfilingSystem; }
//////////////////////////////////////////////////////////////////////////
// retrieves the perlin noise singleton instance
CPNoise3* GetNoiseGen() override;
@@ -324,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
@@ -344,8 +321,6 @@ private:
// Release all resources.
void ShutDown();
bool LoadEngineDLLs();
//! @name Initialization routines
//@{
bool InitConsole();
@@ -361,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();
@@ -380,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)
@@ -437,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;
@@ -564,8 +531,6 @@ private: // ------------------------------------------------------
ESystemConfigSpec m_nMaxConfigSpec;
ESystemConfigPlatform m_ConfigPlatform;
CProfilingSystem m_ProfilingSystem;
// Pause mode.
bool m_bPaused;
bool m_bNoUpdate;
@@ -588,8 +553,6 @@ public:
const SFileVersion& GetProductVersion() override;
const SFileVersion& GetBuildVersion() override;
bool InitVTuneProfiler();
void OpenPlatformPaks();
void OpenLanguagePak(const char* sLanguage);
void OpenLanguageAudioPak(const char* sLanguage);
-262
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
@@ -70,9 +69,6 @@
#include "windows.h"
#include <float.h>
// To enable profiling with vtune (https://software.intel.com/en-us/intel-vtune-amplifier-xe), make sure the line below is not commented out
//#define PROFILE_WITH_VTUNE
#endif //WIN32
#include <IRenderer.h>
@@ -171,34 +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_MODULE_INIT_ISYSTEM "ModuleInitISystem"
# define DLL_MODULE_SHUTDOWN_ISYSTEM "ModuleShutdownISystem"
# 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_MODULE_INIT_ISYSTEM (LPCSTR)2
# define DLL_MODULE_SHUTDOWN_ISYSTEM (LPCSTR)3
# 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
@@ -292,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)
@@ -405,117 +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;
}
//////////////////////////////////////////////////////////////////////////
// After loading DLL initialize it by calling ModuleInitISystem
//////////////////////////////////////////////////////////////////////////
AZStd::string moduleName = PathUtil::GetFileName(dllName);
typedef void*(*PtrFunc_ModuleInitISystem)(ISystem* pSystem, const char* moduleName);
PtrFunc_ModuleInitISystem pfnModuleInitISystem = handle->GetFunction<PtrFunc_ModuleInitISystem>(DLL_MODULE_INIT_ISYSTEM);
if (pfnModuleInitISystem)
{
pfnModuleInitISystem(this, moduleName.c_str());
}
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)
{
typedef void*( * PtrFunc_ModuleShutdownISystem )(ISystem* pSystem);
PtrFunc_ModuleShutdownISystem pfnModuleShutdownISystem = iterator->second->GetFunction<PtrFunc_ModuleShutdownISystem>(DLL_MODULE_SHUTDOWN_ISYSTEM);
if (pfnModuleShutdownISystem)
{
pfnModuleShutdownISystem(this);
}
if (iterator->second->IsLoaded())
{
iterator->second->Unload();
}
iterator->second.release();
}
m_moduleDLLHandles.clear();
#endif // !defined(AZ_MONOLITHIC_BUILD)
}
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
bool CSystem::InitConsole()
@@ -704,33 +471,6 @@ bool CSystem::InitAudioSystem(const SSystemInitParams& initParams)
return result;
}
//////////////////////////////////////////////////////////////////////////
bool CSystem::InitVTuneProfiler()
{
#ifdef PROFILE_WITH_VTUNE
WIN_HMODULE hModule = LoadDLL("VTuneApi.dll");
if (!hModule)
{
return false;
}
VTPause = (VTuneFunction) CryGetProcAddress(hModule, "VTPause");
VTResume = (VTuneFunction) CryGetProcAddress(hModule, "VTResume");
if (!VTPause || !VTResume)
{
AZ_Assert(false, "VTune did not initialize correctly.")
return false;
}
else
{
AZ_TracePrintf(AZ_TRACE_SYSTEM_WINDOW, "VTune API Initialized");
}
#endif //PROFILE_WITH_VTUNE
return true;
}
//////////////////////////////////////////////////////////////////////////
void CSystem::InitLocalization()
{
@@ -1705,8 +1445,6 @@ void CSystem::CreateSystemVars()
m_sys_memory_debug = REGISTER_INT("sys_memory_debug", 0, VF_CHEAT,
"Enables to activate low memory situation is specific places in the code (argument defines which place), 0=off");
REGISTER_CVAR2("sys_vtune", &g_cvars.sys_vtune, 0, VF_NULL, "");
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_17
#include AZ_RESTRICTED_FILE(SystemInit_cpp)
@@ -4140,11 +4140,19 @@ struct LockedFileTest
switch (message.GetMessageType())
{
case SourceFileNotificationMessage::MessageType:
if (const auto sourceFileMessage = azrtti_cast<const SourceFileNotificationMessage*>(&message);
sourceFileMessage != nullptr && sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved
&& m_callback)
if (const auto sourceFileMessage = azrtti_cast<const SourceFileNotificationMessage*>(&message); sourceFileMessage != nullptr &&
sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved)
{
m_callback();
// The File Remove message will occur before an attempt to delete the file
// Wait for more than 1 File Remove message.
// This indicates the AP has attempted to delete the file once, failed to do so and is now retrying
++m_deleteCounter;
if(m_deleteCounter > 1 && m_callback)
{
m_callback();
m_callback = {}; // Unset it to be safe, we only intend to run the callback once
}
}
break;
default:
@@ -4168,6 +4176,7 @@ struct LockedFileTest
ModtimeScanningTest::TearDown();
}
AZStd::atomic_int m_deleteCounter{ 0 };
AZStd::function<void()> m_callback;
};
@@ -4207,6 +4216,10 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeleteFails)
TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
{
// This test is intended to verify the AP will successfully retry deleting a source asset
// when one of its product assets is locked temporarily
// We'll lock the file by holding it open
auto theFile = m_data->m_absolutePath[1].toUtf8();
const char* theFileString = theFile.constData();
auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
@@ -4219,19 +4232,22 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
ASSERT_GT(m_data->m_productPaths.size(), 0);
QFile product(productPath);
// Open the file and keep it open to lock it
// We'll start a thread later to unlock the file
// This will allow us to test how AP handles trying to delete a locked file
ASSERT_TRUE(product.open(QIODevice::ReadOnly));
// Check if we can delete the file now, if we can't, proceed with the test
// If we can, it means the OS running this test doesn't lock open files so there's nothing to test
if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData()))
{
AZStd::thread workerThread;
m_deleteCounter = 0;
m_callback = [&product, &workerThread]() {
workerThread = AZStd::thread([&product]() {
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(60));
product.close();
});
// Set up a callback which will fire after at least 1 retry
// Unlock the file at that point so AP can successfully delete it
m_callback = [&product]()
{
product.close();
};
QMetaObject::invokeMethod(
@@ -4241,8 +4257,9 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
EXPECT_FALSE(QFile::exists(productPath));
EXPECT_EQ(m_data->m_deletedSources.size(), 1);
workerThread.join();
EXPECT_GT(m_deleteCounter, 1); // Make sure the AP tried more than once to delete the file
m_errorAbsorber->ExpectAsserts(0);
}
else
{
@@ -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;
+2
View File
@@ -35,3 +35,5 @@ ly_add_target(
AZ::AzCore
AZ::GridMate
)
ly_add_dependencies(LuaIDE GridHub)
@@ -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;
@@ -659,16 +659,17 @@ namespace AZ
AuxGeomIndex vertexOffset = aznumeric_cast<AuxGeomIndex>(primBuffer.m_vertexBuffer.size());
AuxGeomIndex indexOffset = aznumeric_cast<AuxGeomIndex>(primBuffer.m_indexBuffer.size());
const size_t vertexCountTotal = aznumeric_cast<size_t>(vertexOffset) + vertexCount;
if (aznumeric_cast<size_t>(vertexOffset) + vertexCount > MaxDynamicVertexCount)
if (vertexCountTotal > MaxDynamicVertexCount)
{
AZ_WarningOnce("AuxGeom", false, "Draw function ignored, would exceed maximum allowed index of %d", MaxDynamicVertexCount);
return;
}
AZ::Vector3 center(0.0f, 0.0f, 0.0f);
primBuffer.m_vertexBuffer.reserve(vertexCount);
primBuffer.m_indexBuffer.reserve(vertexCount);
primBuffer.m_vertexBuffer.reserve(vertexCountTotal);
primBuffer.m_indexBuffer.reserve(vertexCountTotal);
for (uint32_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
{
AZ::u32 packedColor = packedColorFunction(vertexIndex);
@@ -734,15 +735,16 @@ namespace AZ
AuxGeomIndex vertexOffset = aznumeric_cast<AuxGeomIndex>(primBuffer.m_vertexBuffer.size());
AuxGeomIndex indexOffset = aznumeric_cast<AuxGeomIndex>(primBuffer.m_indexBuffer.size());
const size_t vertexCountTotal = aznumeric_cast<size_t>(vertexOffset) + vertexCount;
if (aznumeric_cast<size_t>(vertexOffset) + vertexCount > MaxDynamicVertexCount)
if (vertexCountTotal > MaxDynamicVertexCount)
{
AZ_WarningOnce("AuxGeom", false, "Draw function ignored, would exceed maximum allowed index of %d", MaxDynamicVertexCount);
return;
}
AZ::Vector3 center(0.0f, 0.0f, 0.0f);
primBuffer.m_vertexBuffer.reserve(vertexCount);
primBuffer.m_vertexBuffer.reserve(vertexCountTotal);
for (uint32_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
{
AZ::u32 packedColor = packedColorFunction(vertexIndex);
@@ -753,7 +755,7 @@ namespace AZ
}
center /= aznumeric_cast<float>(vertexCount);
primBuffer.m_indexBuffer.reserve(indexCount);
primBuffer.m_indexBuffer.reserve(indexCount + indexOffset);
for (uint32_t index = 0; index < indexCount; ++index)
{
primBuffer.m_indexBuffer.push_back(vertexOffset + indexFunction(index));
@@ -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();
}
@@ -82,9 +82,6 @@ namespace AZ
// ALT+ENTER fullscreen switching using IDXGIFactory::MakeWindowAssociation (see also implementation of SwapChain::PresentInternal).
// You must call the MakeWindowAssociation method after the creation of the swap chain, and on the factory object associated with the
// target HWND swap chain, which you can guarantee by calling the IDXGIObject::GetParent method on the swap chain to locate the factory.
//
// ToDo: ATOM-14673 We should handle ALT+ENTER in the windows message loop and call AzFramework::NativeWindow::ToggleFullScreenState in
// response, but that will have to wait until the WndProc function moves out of CrySystem (ideally into AzFramework::ApplicationWindows).
IDXGIFactoryX* parentFactory = nullptr;
m_swapChain->GetParent(__uuidof(IDXGIFactoryX), (void **)&parentFactory);
DX12::AssertSuccess(parentFactory->MakeWindowAssociation(reinterpret_cast<HWND>(window), DXGI_MWA_NO_ALT_ENTER));
+1
View File
@@ -160,6 +160,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
Gem::Atom_RPI.Public
Gem::Atom_RHI.Public
Gem::Atom_RPI.Edit
Gem::Atom_Utils.TestUtils.Static
)
ly_add_googletest(
NAME Gem::Atom_RPI.Tests
@@ -209,10 +209,6 @@ namespace AZ
//! Traversal will stop once all properties have been enumerated or the callback function returns false
void EnumeratePropertiesInDisplayOrder(const EnumeratePropertiesCallback& callback) const;
//! Convert the property value into the format that will be stored in the source data
//! This is primarily needed to support conversions of special types like enums and images
bool ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const;
Outcome<Data::Asset<MaterialTypeAsset>> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const;
//! Possibly renames @propertyId based on the material version update steps.
@@ -154,6 +154,8 @@ namespace AZ
ConstPtr<RHI::PipelineLibraryData> LoadPipelineLibrary() const;
void SavePipelineLibrary() const;
const ShaderVariant& GetVariantInternal(ShaderVariantStableId shaderVariantStableId);
///////////////////////////////////////////////////////////////////
/// AssetBus overrides
@@ -24,6 +24,9 @@ namespace AZ
class ShaderReloadDebugTracker final
{
public:
static void Init();
static void Shutdown();
static bool IsEnabled();
//! Begin a code section. Will print a "[BEGIN] <sectionName>" header, and all subsequent calls will be indented.
@@ -34,8 +37,8 @@ namespace AZ
if (IsEnabled())
{
const AZStd::string sectionName = AZStd::string::format(sectionNameFormat, args...);
AZ_TracePrintf("ShaderReloadDebug", "%*s [BEGIN] %s \n", s_indent, "", sectionName.c_str());
s_indent += IndentSpaces;
AZ_TracePrintf("ShaderReloadDebug", "%*s [BEGIN] %s \n", GetIndent(), "", sectionName.c_str());
AddIndent();
}
#endif
}
@@ -48,8 +51,8 @@ namespace AZ
if (IsEnabled())
{
const AZStd::string sectionName = AZStd::string::format(sectionNameFormat, args...);
s_indent -= IndentSpaces;
AZ_TracePrintf("ShaderReloadDebug", "%*s [_END_] %s \n", s_indent, "", sectionName.c_str());
RemoveIndent();
AZ_TracePrintf("ShaderReloadDebug", "%*s [_END_] %s \n", GetIndent(), "", sectionName.c_str());
}
#endif
}
@@ -63,7 +66,7 @@ namespace AZ
{
const AZStd::string message = AZStd::string::format(format, args...);
AZ_TracePrintf("ShaderReloadDebug", "%*s %s \n", s_indent, "", message.c_str());
AZ_TracePrintf("ShaderReloadDebug", "%*s %s \n", GetIndent(), "", message.c_str());
}
#endif
}
@@ -86,9 +89,12 @@ namespace AZ
};
private:
static bool s_enabled;
static int s_indent;
static constexpr int IndentSpaces = 4;
static void MakeReady();
static void AddIndent();
static void RemoveIndent();
static int GetIndent();
};
} // namespace RPI
@@ -96,7 +96,7 @@ namespace AZ
//! Return the timestamp when the shader asset was built.
//! This is used to synchronize versions of the ShaderAsset and ShaderVariantTreeAsset, especially during hot-reload.
AZStd::sys_time_t GetShaderAssetBuildTimestamp() const;
AZStd::sys_time_t GetBuildTimestamp() const;
//! Returns the shader option group layout.
const ShaderOptionGroupLayout* GetShaderOptionGroupLayout() const;
@@ -297,7 +297,7 @@ namespace AZ
Name m_drawListName;
//! Use to synchronize versions of the ShaderAsset and ShaderVariantTreeAsset, especially during hot-reload.
AZStd::sys_time_t m_shaderAssetBuildTimestamp = 0;
AZStd::sys_time_t m_buildTimestamp = 0;
///////////////////////////////////////////////////////////////////
@@ -300,48 +300,6 @@ namespace AZ
}
}
bool MaterialTypeSourceData::ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const
{
if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && propertyValue.Is<uint32_t>())
{
const uint32_t index = propertyValue.GetValue<uint32_t>();
if (index >= propertyDefinition.m_enumValues.size())
{
AZ_Error("Material source data", false, "Invalid value for material enum property: '%s'.", propertyDefinition.m_name.c_str());
return false;
}
propertyValue = propertyDefinition.m_enumValues[index];
return true;
}
// Image asset references must be converted from asset IDs to a relative source file path
if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Image && propertyValue.Is<Data::Asset<ImageAsset>>())
{
const Data::Asset<ImageAsset>& imageAsset = propertyValue.GetValue<Data::Asset<ImageAsset>>();
Data::AssetInfo imageAssetInfo;
if (imageAsset.GetId().IsValid())
{
bool result = false;
AZStd::string rootFilePath;
const AZStd::string platformName = ""; // Empty for default
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAssetInfoById,
imageAsset.GetId(), imageAsset.GetType(), platformName, imageAssetInfo, rootFilePath);
if (!result)
{
AZ_Error("Material source data", false, "Image asset could not be found for property: '%s'.", propertyDefinition.m_name.c_str());
return false;
}
}
propertyValue = imageAssetInfo.m_relativePath;
return true;
}
return true;
}
Outcome<Data::Asset<MaterialTypeAsset>> MaterialTypeSourceData::CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath, bool elevateWarnings) const
{
MaterialTypeAssetCreator materialTypeAssetCreator;
@@ -9,6 +9,7 @@
#include <Atom/RPI.Public/Pass/FullscreenTrianglePass.h>
#include <Atom/RPI.Public/Pass/PassUtils.h>
#include <Atom/RPI.Public/RPIUtils.h>
#include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
#include <Atom/RPI.Reflect/Pass/FullscreenTrianglePassData.h>
#include <Atom/RPI.Reflect/Pass/PassTemplate.h>
@@ -46,16 +47,19 @@ namespace AZ
void FullscreenTrianglePass::OnShaderReinitialized(const Shader&)
{
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::OnShaderReinitialized", this);
LoadShader();
}
void FullscreenTrianglePass::OnShaderAssetReinitialized(const Data::Asset<ShaderAsset>&)
{
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::OnShaderAssetReinitialized", this);
LoadShader();
}
void FullscreenTrianglePass::OnShaderVariantReinitialized(const ShaderVariant&)
{
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::OnShaderVariantReinitialized", this);
LoadShader();
}
@@ -129,6 +133,8 @@ namespace AZ
void FullscreenTrianglePass::InitializeInternal()
{
RenderPass::InitializeInternal();
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::InitializeInternal", this);
// This draw item purposefully does not reference any geometry buffers.
// Instead it's expected that the extended class uses a vertex shader
@@ -216,8 +216,6 @@ namespace AZ
void RasterPass::CompileResources(const RHI::FrameGraphCompileContext& context)
{
AZ_PROFILE_SCOPE(RPI, "RasterPass: CompileResources");
if (m_shaderResourceGroup == nullptr)
{
return;
@@ -320,6 +320,30 @@ namespace AZ
}
const ShaderVariant& Shader::GetVariant(ShaderVariantStableId shaderVariantStableId)
{
const ShaderVariant& variant = GetVariantInternal(shaderVariantStableId);
if (ShaderReloadDebugTracker::IsEnabled())
{
auto makeTimeString = [](AZStd::sys_time_t timestamp, AZStd::sys_time_t now)
{
AZStd::sys_time_t elapsedMicroseconds = now - timestamp;
double elapsedSeconds = aznumeric_cast<double>(elapsedMicroseconds / 1'000'000);
AZStd::string timeString = AZStd::string::format("%lld (%f seconds ago)", timestamp, elapsedSeconds);
return timeString;
};
AZStd::sys_time_t now = AZStd::GetTimeNowMicroSecond();
ShaderReloadDebugTracker::Printf("{%p}->Shader::GetVariant for shader '%s' [build time %s] found variant '%s' [build time %s]", this,
m_asset.GetHint().c_str(), makeTimeString(m_asset->GetBuildTimestamp(), now).c_str(),
variant.GetShaderVariantAsset().GetHint().c_str(), makeTimeString(variant.GetShaderVariantAsset()->GetBuildTimestamp(), now).c_str());
}
return variant;
}
const ShaderVariant& Shader::GetVariantInternal(ShaderVariantStableId shaderVariantStableId)
{
if (!shaderVariantStableId.IsValid() || shaderVariantStableId == ShaderAsset::RootShaderVariantStableId)
{
@@ -336,7 +360,7 @@ namespace AZ
// reloaded, but some (or all) shader variants haven't been built yet. Since we want to use the latest version of the
// shader code, ignore the old variants and fall back to the newer root variant instead. There's no need to report a
// warning here because m_asset->GetVariant below will report one.
if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp())
if (findIt->second.GetBuildTimestamp() >= m_asset->GetBuildTimestamp())
{
return findIt->second;
}
@@ -359,7 +383,7 @@ namespace AZ
auto findIt = m_shaderVariants.find(shaderVariantStableId);
if (findIt != m_shaderVariants.end())
{
if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp())
if (findIt->second.GetBuildTimestamp() >= m_asset->GetBuildTimestamp())
{
return findIt->second;
}
@@ -7,24 +7,75 @@
*/
#include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
#include <AzCore/Module/Environment.h>
namespace AZ
{
namespace RPI
{
bool ShaderReloadDebugTracker::s_enabled = false;
int ShaderReloadDebugTracker::s_indent = 0;
namespace ShaderReloadDebugTrackerInternal
{
static const char EnabledVariableName[] = "ShaderReloadDebugTracker enabled";
static const char IndentVariableName[] = "ShaderReloadDebugTracker indent";
static EnvironmentVariable<bool> s_enabled;
static EnvironmentVariable<int> s_indent;
}
void ShaderReloadDebugTracker::Init()
{
ShaderReloadDebugTrackerInternal::s_enabled = AZ::Environment::CreateVariable<bool>(ShaderReloadDebugTrackerInternal::EnabledVariableName);
ShaderReloadDebugTrackerInternal::s_indent = AZ::Environment::CreateVariable<int>(ShaderReloadDebugTrackerInternal::IndentVariableName);
ShaderReloadDebugTrackerInternal::s_enabled.Get() = false;
ShaderReloadDebugTrackerInternal::s_indent.Get() = 0;
}
void ShaderReloadDebugTracker::Shutdown()
{
ShaderReloadDebugTrackerInternal::s_enabled.Reset();
ShaderReloadDebugTrackerInternal::s_indent.Reset();
}
void ShaderReloadDebugTracker::MakeReady()
{
if (!ShaderReloadDebugTrackerInternal::s_enabled.IsValid())
{
ShaderReloadDebugTrackerInternal::s_enabled = AZ::Environment::FindVariable<bool>(ShaderReloadDebugTrackerInternal::EnabledVariableName);
ShaderReloadDebugTrackerInternal::s_indent = AZ::Environment::FindVariable<int>(ShaderReloadDebugTrackerInternal::IndentVariableName);
}
}
bool ShaderReloadDebugTracker::IsEnabled()
{
#ifdef AZ_ENABLE_SHADER_RELOAD_DEBUG_TRACKER
MakeReady();
// Set this to true in the debugger to turn on hot reload tracing.
// If needed, we could hook this up to a CVar.
return s_enabled;
return ShaderReloadDebugTrackerInternal::s_enabled.Get();
#else
return false;
#endif
}
void ShaderReloadDebugTracker::AddIndent()
{
MakeReady();
ShaderReloadDebugTrackerInternal::s_indent.Get() += IndentSpaces;
}
void ShaderReloadDebugTracker::RemoveIndent()
{
MakeReady();
ShaderReloadDebugTrackerInternal::s_indent.Get() -= IndentSpaces;
}
int ShaderReloadDebugTracker::GetIndent()
{
MakeReady();
return ShaderReloadDebugTrackerInternal::s_indent.Get();
}
ShaderReloadDebugTracker::ScopedSection::~ScopedSection()
{
@@ -10,6 +10,7 @@
#include <Atom/RPI.Public/Shader/Shader.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroupPool.h>
#include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
@@ -86,10 +87,13 @@ namespace AZ
};
Data::InstanceDatabase<ShaderResourceGroupPool>::Create(azrtti_typeid<ShaderResourceGroupPool>(), handler, false);
}
ShaderReloadDebugTracker::Init();
}
void ShaderSystem::Shutdown()
{
ShaderReloadDebugTracker::Shutdown();
Data::InstanceDatabase<Shader>::Destroy();
Data::InstanceDatabase<ShaderResourceGroup>::Destroy();
Data::InstanceDatabase<ShaderResourceGroupPool>::Destroy();
@@ -100,7 +100,7 @@ namespace AZ
->Field("pipelineStateType", &ShaderAsset::m_pipelineStateType)
->Field("shaderOptionGroupLayout", &ShaderAsset::m_shaderOptionGroupLayout)
->Field("drawListName", &ShaderAsset::m_drawListName)
->Field("shaderAssetBuildTimestamp", &ShaderAsset::m_shaderAssetBuildTimestamp)
->Field("shaderAssetBuildTimestamp", &ShaderAsset::m_buildTimestamp)
->Field("perAPIShaderData", &ShaderAsset::m_perAPIShaderData)
;
}
@@ -134,11 +134,11 @@ namespace AZ
return m_drawListName;
}
AZStd::sys_time_t ShaderAsset::GetShaderAssetBuildTimestamp() const
AZStd::sys_time_t ShaderAsset::GetBuildTimestamp() const
{
return m_shaderAssetBuildTimestamp;
return m_buildTimestamp;
}
void ShaderAsset::SetReady()
{
m_status = AssetStatus::Ready;
@@ -256,7 +256,7 @@ namespace AZ
}
return GetRootVariant(supervariantIndex);
}
else if (variant->GetBuildTimestamp() >= m_shaderAssetBuildTimestamp)
else if (variant->GetBuildTimestamp() >= m_buildTimestamp)
{
return variant;
}
@@ -21,7 +21,7 @@ namespace AZ
{
if (ValidateIsReady())
{
m_asset->m_shaderAssetBuildTimestamp = shaderAssetBuildTimestamp;
m_asset->m_buildTimestamp = shaderAssetBuildTimestamp;
}
}
@@ -390,7 +390,7 @@ namespace AZ
m_asset->m_pipelineStateType = sourceShaderAsset.m_pipelineStateType;
m_asset->m_drawListName = sourceShaderAsset.m_drawListName;
m_asset->m_shaderOptionGroupLayout = sourceShaderAsset.m_shaderOptionGroupLayout;
m_asset->m_shaderAssetBuildTimestamp = sourceShaderAsset.m_shaderAssetBuildTimestamp;
m_asset->m_buildTimestamp = sourceShaderAsset.m_buildTimestamp;
// copy root variant assets
for (auto& perAPIShaderData : sourceShaderAsset.m_perAPIShaderData)
@@ -21,7 +21,7 @@
#include <Common/AssetManagerTestFixture.h>
#include <Common/RHI/Stubs.h>
#include <Common/RHI/Factory.h>
#include <Common/AssetSystemStub.h>
#include <Atom/Utils/TestUtils/AssetSystemStub.h>
namespace UnitTest
{
@@ -10,8 +10,6 @@ set(FILES
Tests/Buffer/BufferTests.cpp
Tests/Common/AssetManagerTestFixture.cpp
Tests/Common/AssetManagerTestFixture.h
Tests/Common/AssetSystemStub.cpp
Tests/Common/AssetSystemStub.h
Tests/Common/ErrorMessageFinder.cpp
Tests/Common/ErrorMessageFinder.h
Tests/Common/ErrorMessageFinderTests.cpp
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0a1f8d75dcd85e8b4aa57f6c0c81af0300ff96915ba3c2b591095c215d5e1d8c
size 12072
@@ -61,3 +61,31 @@ ly_add_target(
PRIVATE
Gem::AtomToolsFramework.Static
)
################################################################################
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME AtomToolsFramework.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
FILES_CMAKE
atomtoolsframework_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
Tests
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::AzTestShared
Gem::AtomToolsFramework.Static
Gem::Atom_Utils.TestUtils.Static
)
ly_add_googletest(
NAME Gem::AtomToolsFramework.Tests
)
endif()
@@ -7,11 +7,12 @@
*/
#pragma once
#include <AzCore/std/any.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertyValue.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/any.h>
namespace AzToolsFramework
{
@@ -35,12 +36,31 @@ namespace AtomToolsFramework
//! Convert and assign material property meta data fields to editor dynamic property configuration
void ConvertToPropertyConfig(AtomToolsFramework::DynamicPropertyConfig& propertyConfig, const AZ::RPI::MaterialPropertyDynamicMetadata& propertyMetaData);
//! Convert and assign editor dynamic property configuration fields to material property meta data
//! Convert and assign editor dynamic property configuration fields to material property meta data
void ConvertToPropertyMetaData(AZ::RPI::MaterialPropertyDynamicMetadata& propertyMetaData, const AtomToolsFramework::DynamicPropertyConfig& propertyConfig);
//! Compare equality of data types and values of editor property stored in AZStd::any
bool ArePropertyValuesEqual(const AZStd::any& valueA, const AZStd::any& valueB);
//! Convert the property value into the format that will be stored in the source data
//! This is primarily needed to support conversions of special types like enums and images
//! @param exportPath absolute path of the file being saved
//! @param propertyDefinition describes type information and other details about propertyValue
//! @param propertyValue the value being converted before saving
bool ConvertToExportFormat(
const AZStd::string& exportPath,
const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition,
AZ::RPI::MaterialPropertyValue& propertyValue);
//! Generate a file path from the exported file to the external reference.
//! This function returns a relative path from the export file to the reference file.
//! If the relative path is too different or distant from the export path then we return the asset folder relative path.
//! @param exportPath absolute path of the file being saved
//! @param referencePath absolute path of a file that will be treated as an external reference
//! @param maxPathDepth the maximum relative depth or number of parent or child folders between the export path and the reference path
AZStd::string GetExteralReferencePath(
const AZStd::string& exportPath, const AZStd::string& referencePath, const uint32_t maxPathDepth = 2);
//! Traverse up the instance data node hierarchy to find the containing dynamic property object
const AtomToolsFramework::DynamicProperty* FindDynamicPropertyForInstanceDataNode(const AzToolsFramework::InstanceDataNode* pNode);
} // namespace AtomToolsFramework
@@ -6,9 +6,10 @@
*
*/
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Reflect/Image/ImageAsset.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
@@ -18,6 +19,7 @@
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector4.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/UI/PropertyEditor/InstanceDataHierarchy.h>
namespace AtomToolsFramework
@@ -163,6 +165,88 @@ namespace AtomToolsFramework
return false;
}
bool ConvertToExportFormat(
const AZStd::string& exportPath,
const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition,
AZ::RPI::MaterialPropertyValue& propertyValue)
{
if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && propertyValue.Is<uint32_t>())
{
const uint32_t index = propertyValue.GetValue<uint32_t>();
if (index >= propertyDefinition.m_enumValues.size())
{
AZ_Error("AtomToolsFramework", false, "Invalid value for material enum property: '%s'.", propertyDefinition.m_name.c_str());
return false;
}
propertyValue = propertyDefinition.m_enumValues[index];
return true;
}
// Image asset references must be converted from asset IDs to a relative source file path
if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Image)
{
if (propertyValue.Is<AZ::Data::Asset<AZ::RPI::ImageAsset>>())
{
const auto& imageAsset = propertyValue.GetValue<AZ::Data::Asset<AZ::RPI::ImageAsset>>();
const auto& imagePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(imageAsset.GetId());
propertyValue = GetExteralReferencePath(exportPath, imagePath);
return true;
}
if (propertyValue.Is<AZ::Data::Instance<AZ::RPI::Image>>())
{
const auto& image = propertyValue.GetValue<AZ::Data::Instance<AZ::RPI::Image>>();
const auto& imagePath = image ? AZ::RPI::AssetUtils::GetSourcePathByAssetId(image->GetAssetId()) : "";
propertyValue = GetExteralReferencePath(exportPath, imagePath);
return true;
}
}
return true;
}
AZStd::string GetExteralReferencePath(const AZStd::string& exportPath, const AZStd::string& referencePath, const uint32_t maxPathDepth)
{
if (referencePath.empty())
{
return {};
}
AZ::IO::BasicPath<AZStd::string> exportFolder(exportPath);
exportFolder.RemoveFilename();
const AZStd::string relativePath = AZ::IO::PathView(referencePath).LexicallyRelative(exportFolder).StringAsPosix();
// Count the difference in depth between the export file path and the referenced file path.
uint32_t parentFolderCount = 0;
AZStd::string::size_type pos = 0;
const AZStd::string parentFolderToken = "..";
while ((pos = relativePath.find(parentFolderToken, pos)) != AZStd::string::npos)
{
parentFolderCount++;
pos += parentFolderToken.length();
}
// If the difference in depth is too great then revert to using the asset folder relative path.
// We could change this to only use relative paths for references in subfolders.
if (parentFolderCount > maxPathDepth)
{
AZStd::string watchFolder;
AZ::Data::AssetInfo assetInfo;
bool sourceInfoFound = false;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, referencePath.c_str(),
assetInfo, watchFolder);
if (sourceInfoFound)
{
return assetInfo.m_relativePath;
}
}
return relativePath;
}
const AtomToolsFramework::DynamicProperty* FindDynamicPropertyForInstanceDataNode(const AzToolsFramework::InstanceDataNode* pNode)
{
// Traverse up the hierarchy from the input node to search for an instance corresponding to material inspector property
@@ -172,7 +256,8 @@ namespace AtomToolsFramework
const AZ::SerializeContext::ClassData* classData = currentNode->GetClassMetadata();
if (context && classData)
{
if (context->CanDowncast(classData->m_typeId, azrtti_typeid<AtomToolsFramework::DynamicProperty>(), classData->m_azRtti, nullptr))
if (context->CanDowncast(
classData->m_typeId, azrtti_typeid<AtomToolsFramework::DynamicProperty>(), classData->m_azRtti, nullptr))
{
return static_cast<const AtomToolsFramework::DynamicProperty*>(currentNode->FirstInstance());
}
@@ -7,25 +7,71 @@
*/
#include <AzTest/AzTest.h>
#include <Atom/Utils/TestUtils/AssetSystemStub.h>
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
class AtomToolsFrameworkTest
: public ::testing::Test
namespace UnitTest
{
protected:
void SetUp() override
class AtomToolsFrameworkTest : public ::testing::Test
{
protected:
void SetUp() override
{
if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Create(AZ::SystemAllocator::Descriptor());
}
m_assetSystemStub.Activate();
RegisterSourceAsset("objects/upgrades/materials/supercondor.material");
RegisterSourceAsset("materials/condor.material");
RegisterSourceAsset("materials/talisman.material");
RegisterSourceAsset("materials/city.material");
RegisterSourceAsset("materials/totem.material");
RegisterSourceAsset("textures/orange.png");
RegisterSourceAsset("textures/red.png");
RegisterSourceAsset("textures/gold.png");
RegisterSourceAsset("textures/fuzz.png");
}
void TearDown() override
{
m_assetSystemStub.Deactivate();
if (AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
}
void RegisterSourceAsset(const AZStd::string& path)
{
const AZ::IO::BasicPath assetRootPath = AZ::IO::PathView(m_assetRoot).LexicallyNormal();
const AZ::IO::BasicPath normalizedPath = AZ::IO::BasicPath(assetRootPath).Append(path).LexicallyNormal();
AZ::Data::AssetInfo assetInfo = {};
assetInfo.m_assetId = AZ::Uuid::CreateRandom();
assetInfo.m_relativePath = normalizedPath.LexicallyRelative(assetRootPath).StringAsPosix();
m_assetSystemStub.RegisterSourceInfo(normalizedPath.StringAsPosix().c_str(), assetInfo, assetRootPath.StringAsPosix().c_str());
}
static constexpr const char* m_assetRoot = "d:/project/assets/";
AssetSystemStub m_assetSystemStub;
};
TEST_F(AtomToolsFrameworkTest, GetExteralReferencePath_Succeeds)
{
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("", "", 2), "");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/materials/condor.material", "", 2), "");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/materials/talisman.material", "", 2), "");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/materials/talisman.material", "d:/project/assets/textures/gold.png", 2), "../textures/gold.png");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/materials/talisman.material", "d:/project/assets/textures/gold.png", 0), "textures/gold.png");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 3), "../../../materials/condor.material");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 2), "materials/condor.material");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 1), "materials/condor.material");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 0), "materials/condor.material");
}
void TearDown() override
{
}
};
TEST_F(AtomToolsFrameworkTest, SanityTest)
{
ASSERT_TRUE(true);
}
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
} // namespace UnitTest
@@ -0,0 +1,11 @@
#
# 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
#
#
set(FILES
Tests/AtomToolsFrameworkTest.cpp
)
@@ -230,18 +230,13 @@ namespace MaterialEditor
// create source data from properties
MaterialSourceData sourceData;
sourceData.m_materialType = m_materialSourceData.m_materialType;
sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial;
AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null.");
sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion();
// Force save data to store forward slashes
AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/");
AzFramework::StringFunc::Replace(sourceData.m_parentMaterial, "\\", "/");
sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(m_absolutePath, m_materialSourceData.m_materialType);
sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(m_absolutePath, m_materialSourceData.m_parentMaterial);
// populate sourceData with modified or overwritten properties
const bool savedProperties = SavePropertiesToSourceData(sourceData, [](const AtomToolsFramework::DynamicProperty& property) {
const bool savedProperties = SavePropertiesToSourceData(m_absolutePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property)
{
return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue);
});
@@ -304,18 +299,13 @@ namespace MaterialEditor
// create source data from properties
MaterialSourceData sourceData;
sourceData.m_materialType = m_materialSourceData.m_materialType;
sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial;
AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null.");
sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion();
// Force save data to store forward slashes
AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/");
AzFramework::StringFunc::Replace(sourceData.m_parentMaterial, "\\", "/");
sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_materialType);
sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_parentMaterial);
// populate sourceData with modified or overwritten properties
const bool savedProperties = SavePropertiesToSourceData(sourceData, [](const AtomToolsFramework::DynamicProperty& property) {
const bool savedProperties = SavePropertiesToSourceData(normalizedSavePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property)
{
return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue);
});
@@ -377,23 +367,18 @@ namespace MaterialEditor
// create source data from properties
MaterialSourceData sourceData;
sourceData.m_materialType = m_materialSourceData.m_materialType;
AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null.");
sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion();
sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_materialType);
// Only assign a parent path if the source was a .material
if (AzFramework::StringFunc::Path::IsExtension(m_relativePath.c_str(), MaterialSourceData::Extension))
{
sourceData.m_parentMaterial = m_relativePath;
sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_absolutePath);
}
// Force save data to store forward slashes
AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/");
AzFramework::StringFunc::Replace(sourceData.m_parentMaterial, "\\", "/");
// populate sourceData with modified properties
const bool savedProperties = SavePropertiesToSourceData(sourceData, [](const AtomToolsFramework::DynamicProperty& property) {
const bool savedProperties = SavePropertiesToSourceData(normalizedSavePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property)
{
return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_originalValue);
});
@@ -590,7 +575,8 @@ namespace MaterialEditor
}
}
bool MaterialDocument::SavePropertiesToSourceData(AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const
bool MaterialDocument::SavePropertiesToSourceData(
const AZStd::string& exportPath, AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const
{
using namespace AZ;
using namespace RPI;
@@ -598,7 +584,7 @@ namespace MaterialEditor
bool result = true;
// populate sourceData with properties that meet the filter
m_materialTypeSourceData.EnumerateProperties([this, &sourceData, &propertyFilter, &result](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) {
m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) {
const MaterialPropertyId propertyId(groupName, propertyName);
@@ -608,7 +594,7 @@ namespace MaterialEditor
MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue());
if (propertyValue.IsValid())
{
if (!m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(propertyDefinition, propertyValue))
if (!AtomToolsFramework::ConvertToExportFormat(exportPath, propertyDefinition, propertyValue))
{
AZ_Error("MaterialDocument", false, "Material document property could not be converted: '%s' in '%s'.", propertyId.GetFullName().GetCStr(), m_absolutePath.c_str());
result = false;
@@ -663,8 +649,6 @@ namespace MaterialEditor
return false;
}
AZStd::string materialTypeSourceFilePath;
// The material document and inspector are constructed from source data
if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialSourceData::Extension))
{
@@ -675,13 +659,24 @@ namespace MaterialEditor
return false;
}
// We must also always load the material type data for a complete, ordered set of the
// groups and properties that will be needed for comparison and building the inspector
materialTypeSourceFilePath = AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_materialType);
auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourceFilePath);
// We always need the absolute path for the material type and parent material to load source data and resolving
// relative paths when saving. This will convert and store them as absolute paths for use within the document.
if (!m_materialSourceData.m_parentMaterial.empty())
{
m_materialSourceData.m_parentMaterial =
AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial);
}
if (!m_materialSourceData.m_materialType.empty())
{
m_materialSourceData.m_materialType = AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_materialType);
}
// Load the material type source data which provides the layout and default values of all of the properties
auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(m_materialSourceData.m_materialType);
if (!materialTypeOutcome.IsSuccess())
{
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", materialTypeSourceFilePath.c_str());
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_materialSourceData.m_materialType.c_str());
return false;
}
m_materialTypeSourceData = materialTypeOutcome.GetValue();
@@ -694,10 +689,10 @@ namespace MaterialEditor
}
else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialTypeSourceData::Extension))
{
materialTypeSourceFilePath = m_absolutePath;
// Load the material type source data, which will be used for enumerating properties and building material source data
auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourceFilePath);
// A material document can be created or loaded from material or material type source data. If we are attempting to load
// material type source data then the material source data object can be created just by referencing the document path as the
// material type path.
auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(m_absolutePath);
if (!materialTypeOutcome.IsSuccess())
{
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str());
@@ -705,9 +700,8 @@ namespace MaterialEditor
}
m_materialTypeSourceData = materialTypeOutcome.GetValue();
// The document represents a material, not a material type.
// If the input data is a material type file we have to generate the material source data by referencing it.
m_materialSourceData.m_materialType = m_relativePath;
// We are storing absolute paths in the loaded version of the source data so that the files can be resolved at all times.
m_materialSourceData.m_materialType = m_absolutePath;
m_materialSourceData.m_parentMaterial.clear();
}
else
@@ -870,7 +864,8 @@ namespace MaterialEditor
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig);
}
const MaterialFunctorSourceData::EditorContext editorContext = MaterialFunctorSourceData::EditorContext(materialTypeSourceFilePath, m_materialAsset->GetMaterialPropertiesLayout());
const MaterialFunctorSourceData::EditorContext editorContext =
MaterialFunctorSourceData::EditorContext(m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout());
for (Ptr<MaterialFunctorSourceDataHolder> functorData : m_materialTypeSourceData.m_materialFunctorSourceData)
{
MaterialFunctorSourceData::FunctorResult result2 = functorData->CreateFunctor(editorContext);
@@ -110,7 +110,8 @@ namespace MaterialEditor
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
//////////////////////////////////////////////////////////////////////////
bool SavePropertiesToSourceData(AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const;
bool SavePropertiesToSourceData(
const AZStd::string& exportPath, AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const;
bool OpenInternal(AZStd::string_view loadPath);
+21
View File
@@ -27,6 +27,27 @@ ly_add_target(
3rdParty::libpng
)
if(PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_target(
NAME Atom_Utils.TestUtils.Static STATIC
NAMESPACE Gem
FILES_CMAKE
atom_utils_editor_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
AZ::AtomCore
AZ::AzCore
AZ::AzFramework
AZ::AzToolsFramework
)
endif()
################################################################################
# Tests
################################################################################
@@ -6,7 +6,7 @@
*
*/
#include <Common/AssetSystemStub.h>
#include <Atom/Utils/TestUtils/AssetSystemStub.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace UnitTest

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