Merge branch 'development' into Atom/dmcdiar/ATOM-4126

This commit is contained in:
dmcdiar
2021-09-17 16:28:52 -07:00
221 changed files with 6557 additions and 3422 deletions
@@ -31,7 +31,7 @@ def setup(launcher: pytest.fixture,
Set up the resource mapping configuration and start the log monitor.
:param launcher: Client launcher for running the test level.
:param asset_processor: asset_processor fixture.
:return log monitor object, metrics file path and the metrics stack name.
:return log monitor object.
"""
asset_processor.start()
asset_processor.wait_for_idle()
@@ -73,12 +73,11 @@ def monitor_metrics_submission(log_monitor: pytest.fixture) -> None:
f'unexpected_lines values: {unexpected_lines}')
def query_metrics_from_s3(aws_metrics_utils: pytest.fixture, resource_mappings: pytest.fixture, stack_name: str) -> None:
def query_metrics_from_s3(aws_metrics_utils: pytest.fixture, resource_mappings: pytest.fixture) -> None:
"""
Verify that the metrics events are delivered to the S3 bucket and can be queried.
:param aws_metrics_utils: aws_metrics_utils fixture.
:param resource_mappings: resource_mappings fixture.
:param stack_name: name of the CloudFormation stack.
"""
aws_metrics_utils.verify_s3_delivery(
resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsBucketName')
@@ -89,23 +88,24 @@ def query_metrics_from_s3(aws_metrics_utils: pytest.fixture, resource_mappings:
resource_mappings.get_resource_name_id('AWSMetrics.EventsCrawlerName'))
# Remove the events_json table if exists so that the sample query can create a table with the same name.
aws_metrics_utils.delete_table(f'{stack_name}-eventsdatabase', 'events_json')
aws_metrics_utils.run_named_queries(f'{stack_name}-AthenaWorkGroup')
aws_metrics_utils.delete_table(resource_mappings.get_resource_name_id('AWSMetrics.EventDatabaseName'), 'events_json')
aws_metrics_utils.run_named_queries(resource_mappings.get_resource_name_id('AWSMetrics.AthenaWorkGroupName'))
logger.info('Query metrics from S3 successfully.')
def verify_operational_metrics(aws_metrics_utils: pytest.fixture, stack_name: str, start_time: datetime) -> None:
def verify_operational_metrics(aws_metrics_utils: pytest.fixture,
resource_mappings: pytest.fixture, start_time: datetime) -> None:
"""
Verify that operational health metrics are delivered to CloudWatch.
aws_metrics_utils: aws_metrics_utils fixture.
stack_name: name of the CloudFormation stack.
start_time: Time when the game launcher starts.
:param aws_metrics_utils: aws_metrics_utils fixture.
:param resource_mappings: resource_mappings fixture.
:param start_time: Time when the game launcher starts.
"""
aws_metrics_utils.verify_cloud_watch_delivery(
'AWS/Lambda',
'Invocations',
[{'Name': 'FunctionName',
'Value': f'{stack_name}-AnalyticsProcessingLambda'}],
'Value': resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsProcessingLambdaName')}],
start_time)
logger.info('AnalyticsProcessingLambda metrics are sent to CloudWatch.')
@@ -113,7 +113,7 @@ def verify_operational_metrics(aws_metrics_utils: pytest.fixture, stack_name: st
'AWS/Lambda',
'Invocations',
[{'Name': 'FunctionName',
'Value': f'{stack_name}-EventsProcessingLambda'}],
'Value': resource_mappings.get_resource_name_id('AWSMetrics.EventProcessingLambdaName')}],
start_time)
logger.info('EventsProcessingLambda metrics are sent to CloudWatch.')
@@ -157,7 +157,6 @@ class TestAWSMetricsWindows(object):
workspace: pytest.fixture,
aws_utils: pytest.fixture,
resource_mappings: pytest.fixture,
stacks: typing.List,
aws_metrics_utils: pytest.fixture):
"""
Verify that the metrics events are sent to CloudWatch and S3 for analytics.
@@ -189,10 +188,10 @@ class TestAWSMetricsWindows(object):
operational_threads = list()
operational_threads.append(
AWSMetricsThread(target=query_metrics_from_s3,
args=(aws_metrics_utils, resource_mappings, stacks[0])))
args=(aws_metrics_utils, resource_mappings)))
operational_threads.append(
AWSMetricsThread(target=verify_operational_metrics,
args=(aws_metrics_utils, stacks[0], start_time)))
args=(aws_metrics_utils, resource_mappings, start_time)))
operational_threads.append(
AWSMetricsThread(target=update_kinesis_analytics_application_status,
args=(aws_metrics_utils, resource_mappings, False)))
@@ -20,6 +20,7 @@ import azlmbr.legacy.general as general
# Helper file Imports
from editor_python_test_tools.utils import Report
class EditorComponent:
"""
EditorComponent class used to set and get the component property value using path
@@ -28,7 +29,6 @@ class EditorComponent:
which also assigns self.id and self.type_id to the EditorComponent object.
"""
# Methods
def get_component_name(self) -> str:
"""
Used to get name of component
@@ -87,6 +87,13 @@ class EditorComponent:
outcome.IsSuccess()
), f"Failure: Could not set value to '{self.get_component_name()}' : '{component_property_path}'"
def is_enabled(self):
"""
Used to verify if the component is enabled.
:return: True if enabled, otherwise False.
"""
return editor.EditorComponentAPIBus(bus.Broadcast, "IsComponentEnabled", self.id)
@staticmethod
def get_type_ids(component_names: list) -> list:
"""
@@ -254,7 +261,7 @@ class EditorEntity:
def get_components_of_type(self, component_names: list) -> List[EditorComponent]:
"""
Used to get components of type component_name that already exists on Entity
:param component_name: Name to component to check
:param component_names: List of names of components to check
:return: List of Entity Component objects of given component name
"""
component_list = []
@@ -318,3 +325,39 @@ class EditorEntity:
editor.EditorEntityAPIBus(bus.Event, "SetStartStatus", self.id, status_to_set)
set_status = self.get_start_status()
assert set_status == status_to_set, f"Failed to set start status of {desired_start_status} to {self.get_name}"
def delete(self) -> None:
"""
Used to delete the Entity.
:return: None
"""
editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntityById", self.id)
def set_visibility_state(self, is_visible: bool) -> None:
"""
Sets the visibility state on the object to visible or not visible.
:param is_visible: True for making visible, False to make not visible.
:return: None
"""
editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", self.id, is_visible)
def exists(self) -> bool:
"""
Used to verify if the Entity exists.
:return: True if the Entity exists, False otherwise.
"""
return editor.ToolsApplicationRequestBus(bus.Broadcast, "EntityExists", self.id)
def is_hidden(self) -> bool:
"""
Gets the "isHidden" value from the Entity.
:return: True if "isHidden" is enabled, False otherwise.
"""
return editor.EditorEntityInfoRequestBus(bus.Event, "IsHidden", self.id)
def is_visible(self) -> bool:
"""
Gets the "isVisible" value from the Entity.
:return: True if "isVisible" is enabled, False otherwise.
"""
return editor.EditorEntityInfoRequestBus(bus.Event, "IsVisible", self.id)
@@ -22,6 +22,21 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
AssetProcessor
AutomatedTesting.Assets
Editor
COMPONENT
Atom
)
ly_add_pytest(
NAME AutomatedTesting::AtomRenderer_HydraTests_MainOptimized
TEST_SUITE main
PATH ${CMAKE_CURRENT_LIST_DIR}/test_Atom_MainSuite_Optimized.py
TEST_SERIAL
TIMEOUT 600
RUNTIME_DEPENDENCIES
AssetProcessor
AutomatedTesting.Assets
Editor
COMPONENT
Atom
)
ly_add_pytest(
NAME AutomatedTesting::AtomRenderer_HydraTests_Sandbox
@@ -33,6 +48,8 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
AssetProcessor
AutomatedTesting.Assets
Editor
COMPONENT
Atom
)
ly_add_pytest(
NAME AutomatedTesting::AtomRenderer_HydraTests_GPUTests
@@ -45,6 +62,8 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
AssetProcessor
AutomatedTesting.Assets
Editor
COMPONENT
Atom
)
ly_add_pytest(
NAME AutomatedTesting::AtomRenderer_HydraTests_ShaderBuildPipeline
@@ -0,0 +1,151 @@
"""
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
"""
# fmt: off
class Tests:
camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created")
camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity")
camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component")
creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
decal_creation = ("Decal Entity successfully created", "Decal Entity failed to be created")
decal_component = ("Entity has a Decal component", "Entity failed to find Decal component")
material_property_set = ("Material property set on Decal component", "Couldn't set Material property on Decal 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")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_Decal_AddedToEntity():
"""
Summary:
Tests the Decal 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 Decal entity with no components.
2) Add Decal component to Decal 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) Set Material property on Decal component.
9) Delete Decal entity.
10) UNDO deletion.
11) REDO deletion.
12) Look for errors.
:return: None
"""
import os
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.legacy.general as general
import azlmbr.math as math
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a Decal entity with no components.
decal_name = "Decal (Atom)"
decal_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), decal_name)
Report.critical_result(Tests.decal_creation, decal_entity.exists())
# 2. Add Decal component to Decal entity.
decal_component = decal_entity.add_component(decal_name)
Report.critical_result(Tests.decal_component, decal_entity.has_component(decal_name))
# 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 decal_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, decal_entity.exists())
# 5. Enter/Exit game mode.
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
helper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
decal_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, decal_entity.is_hidden() is True)
# 7. Test IsVisible.
decal_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, decal_entity.is_visible() is True)
# 8. Set Material property on Decal component.
decal_material_property_path = "Controller|Configuration|Material"
decal_material_asset_path = os.path.join("AutomatedTesting", "Materials", "basic_grey.material")
decal_material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", decal_material_asset_path, math.Uuid(), False)
decal_component.set_component_property_value(decal_material_property_path, decal_material_asset)
get_material_property = decal_component.get_component_property_value(decal_material_property_path)
Report.result(Tests.material_property_set, get_material_property == decal_material_asset)
# 9. Delete Decal entity.
decal_entity.delete()
Report.result(Tests.entity_deleted, not decal_entity.exists())
# 10. UNDO deletion.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.deletion_undo, decal_entity.exists())
# 11. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not decal_entity.exists())
# 12. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_Decal_AddedToEntity)
@@ -0,0 +1,173 @@
"""
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
"""
# fmt: off
class Tests:
camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created")
camera_component_added = ("Camera component was added to Camera entity", "Camera component failed to be added to entity")
camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component")
camera_property_set = ("DepthOfField Entity set Camera Entity", "DepthOfField Entity could not set Camera Entity")
creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
depth_of_field_creation = ("DepthOfField Entity successfully created", "DepthOfField Entity failed to be created")
depth_of_field_component = ("Entity has a DepthOfField component", "Entity failed to find DepthOfField component")
depth_of_field_disabled = ("DepthOfField component disabled", "DepthOfField component was not disabled.")
post_fx_component = ("Entity has a Post FX Layer component", "Entity did not have a Post FX Layer component")
depth_of_field_enabled = ("DepthOfField component enabled", "DepthOfField component 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")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_DepthOfField_AddedToEntity():
"""
Summary:
Tests the DepthOfField 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 DepthOfField entity with no components.
2) Add a DepthOfField component to DepthOfField entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Verify DepthOfField component not enabled.
6) Add Post FX Layer component since it is required by the DepthOfField component.
7) Verify DepthOfField component is enabled.
8) Enter/Exit game mode.
9) Test IsHidden.
10) Test IsVisible.
11) Add Camera entity.
12) Add Camera component to Camera entity.
13) Set the DepthOfField components's Camera Entity to the newly created Camera entity.
14) Delete DepthOfField entity.
15) UNDO deletion.
16) REDO deletion.
17) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
import azlmbr.math as math
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a DepthOfField entity with no components.
depth_of_field_name = "DepthOfField"
depth_of_field_entity = EditorEntity.create_editor_entity_at(
math.Vector3(512.0, 512.0, 34.0), depth_of_field_name)
Report.critical_result(Tests.depth_of_field_creation, depth_of_field_entity.exists())
# 2. Add a DepthOfField component to DepthOfField entity.
depth_of_field_component = depth_of_field_entity.add_component(depth_of_field_name)
Report.critical_result(Tests.depth_of_field_component, depth_of_field_entity.has_component(depth_of_field_name))
# 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 depth_of_field_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, depth_of_field_entity.exists())
# 5. Verify DepthOfField component not enabled.
Report.result(Tests.depth_of_field_disabled, not depth_of_field_component.is_enabled())
# 6. Add Post FX Layer component since it is required by the DepthOfField component.
post_fx_layer = "PostFX Layer"
depth_of_field_entity.add_component(post_fx_layer)
Report.result(Tests.post_fx_component, depth_of_field_entity.has_component(post_fx_layer))
# 7. Verify DepthOfField component is enabled.
Report.result(Tests.depth_of_field_enabled, depth_of_field_component.is_enabled())
# 8. Enter/Exit game mode.
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
helper.exit_game_mode(Tests.exit_game_mode)
# 9. Test IsHidden.
depth_of_field_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, depth_of_field_entity.is_hidden() is True)
# 10. Test IsVisible.
depth_of_field_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, depth_of_field_entity.is_visible() is True)
# 11. Add Camera entity.
camera_name = "Camera"
camera_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), camera_name)
Report.result(Tests.camera_creation, camera_entity.exists())
# 12. Add Camera component to Camera entity.
camera_entity.add_component(camera_name)
Report.result(Tests.camera_component_added, camera_entity.has_component(camera_name))
# 13. Set the DepthOfField components's Camera Entity to the newly created Camera entity.
depth_of_field_camera_property_path = "Controller|Configuration|Camera Entity"
depth_of_field_component.set_component_property_value(depth_of_field_camera_property_path, camera_entity.id)
camera_entity_set = depth_of_field_component.get_component_property_value(depth_of_field_camera_property_path)
Report.result(Tests.camera_property_set, camera_entity.id == camera_entity_set)
# 14. Delete DepthOfField entity.
depth_of_field_entity.delete()
Report.result(Tests.entity_deleted, not depth_of_field_entity.exists())
# 15. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, depth_of_field_entity.exists())
# 16. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not depth_of_field_entity.exists())
# 17. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_DepthOfField_AddedToEntity)
@@ -0,0 +1,157 @@
"""
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
"""
# fmt: off
class Tests:
camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created")
camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity")
camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component")
creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
directional_light_creation = ("Directional Light Entity successfully created", "Directional Light Entity failed to be created")
directional_light_component = ("Entity has a Directional Light component", "Entity failed to find Directional Light component")
shadow_camera_check = ("Directional Light component Shadow camera set", "Directional Light component Shadow camera was not set")
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")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_DirectionalLight_AddedToEntity():
"""
Summary:
Tests the Directional Light 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 Directional Light entity with no components.
2) Add Directional Light component to Directional Light 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) Add Camera entity.
9) Add Camera component to Camera entity
10) Set the Directional Light component property Shadow|Camera to the Camera entity.
11) Delete Directional Light entity.
12) UNDO deletion.
13) REDO deletion.
14) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
import azlmbr.math as math
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a Directional Light entity with no components.
directional_light_name = "Directional Light"
directional_light_entity = EditorEntity.create_editor_entity_at(
math.Vector3(512.0, 512.0, 34.0), directional_light_name)
Report.critical_result(Tests.directional_light_creation, directional_light_entity.exists())
# 2. Add Directional Light component to Directional Light entity.
directional_light_component = directional_light_entity.add_component(directional_light_name)
Report.critical_result(
Tests.directional_light_component, directional_light_entity.has_component(directional_light_name))
# 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 directional_light_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, directional_light_entity.exists())
# 5. Enter/Exit game mode.
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
helper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
directional_light_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, directional_light_entity.is_hidden() is True)
# 7. Test IsVisible.
directional_light_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, directional_light_entity.is_visible() is True)
# 8. Add Camera entity.
camera_name = "Camera"
camera_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), camera_name)
Report.result(Tests.camera_creation, camera_entity.exists())
# 9. Add Camera component to Camera entity.
camera_entity.add_component(camera_name)
Report.result(Tests.camera_component_added, camera_entity.has_component(camera_name))
# 10. Set the Directional Light component property Shadow|Camera to the Camera entity.
shadow_camera_property_path = "Controller|Configuration|Shadow|Camera"
directional_light_component.set_component_property_value(shadow_camera_property_path, camera_entity.id)
shadow_camera_set = directional_light_component.get_component_property_value(shadow_camera_property_path)
Report.result(Tests.shadow_camera_check, camera_entity.id == shadow_camera_set)
# 11. Delete DirectionalLight entity.
directional_light_entity.delete()
Report.result(Tests.entity_deleted, not directional_light_entity.exists())
# 12. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, directional_light_entity.exists())
# 13. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not directional_light_entity.exists())
# 14. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_DirectionalLight_AddedToEntity)
@@ -0,0 +1,137 @@
"""
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
"""
# fmt: off
class Tests:
camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created")
camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity")
camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component")
creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
display_mapper_creation = ("Display Mapper Entity successfully created", "Display Mapper Entity failed to be created")
display_mapper_component = ("Entity has a Display Mapper component", "Entity failed to find Display Mapper 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")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_DisplayMapper_AddedToEntity():
"""
Summary:
Tests the Display Mapper 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 Display Mapper entity with no components.
2) Add Display Mapper component to Display Mapper 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 Display Mapper entity.
9) UNDO deletion.
10) REDO deletion.
11) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
import azlmbr.math as math
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a Display Mapper entity with no components.
display_mapper = "Display Mapper"
display_mapper_entity = EditorEntity.create_editor_entity_at(
math.Vector3(512.0, 512.0, 34.0), f"{display_mapper}")
Report.critical_result(Tests.display_mapper_creation, display_mapper_entity.exists())
# 2. Add Display Mapper component to Display Mapper entity.
display_mapper_entity.add_component(display_mapper)
Report.critical_result(Tests.display_mapper_component, display_mapper_entity.has_component(display_mapper))
# 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 display_mapper_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, display_mapper_entity.exists())
# 5. Enter/Exit game mode.
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
helper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
display_mapper_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, display_mapper_entity.is_hidden() is True)
# 7. Test IsVisible.
display_mapper_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, display_mapper_entity.is_visible() is True)
# 8. Delete Display Mapper entity.
display_mapper_entity.delete()
Report.result(Tests.entity_deleted, not display_mapper_entity.exists())
# 9. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, display_mapper_entity.exists())
# 10. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not display_mapper_entity.exists())
# 11. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_DisplayMapper_AddedToEntity)
@@ -0,0 +1,145 @@
"""
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
"""
# fmt: off
class Tests:
camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created")
camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity")
camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component")
creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
exposure_control_creation = ("ExposureControl Entity successfully created", "ExposureControl Entity failed to be created")
exposure_control_component = ("Entity has a Exposure Control component", "Entity failed to find Exposure Control component")
post_fx_component = ("Entity has a Post FX Layer component", "Entity did not have a Post FX Layer 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")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_ExposureControl_AddedToEntity():
"""
Summary:
Tests the Exposure Control 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 Exposure Control entity with no components.
2) Add Exposure Control component to Exposure Control 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) Add Post FX Layer component.
9) Delete Exposure Control entity.
10) UNDO deletion.
11) REDO deletion.
12) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
import azlmbr.math as math
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Creation of Exposure Control entity with no components.
exposure_control_name = "Exposure Control"
exposure_control_entity = EditorEntity.create_editor_entity_at(
math.Vector3(512.0, 512.0, 34.0), f"{exposure_control_name}")
Report.critical_result(Tests.exposure_control_creation, exposure_control_entity.exists())
# 2. Add Exposure Control component to Exposure Control entity.
exposure_control_entity.add_component(exposure_control_name)
Report.critical_result(
Tests.exposure_control_component, exposure_control_entity.has_component(exposure_control_name))
# 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 exposure_control_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, exposure_control_entity.exists())
# 5. Enter/Exit game mode.
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
helper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
exposure_control_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, exposure_control_entity.is_hidden() is True)
# 7. Test IsVisible.
exposure_control_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, exposure_control_entity.is_visible() is True)
# 8. Add Post FX Layer component.
post_fx_layer_name = "PostFX Layer"
exposure_control_entity.add_component(post_fx_layer_name)
Report.result(Tests.post_fx_component, exposure_control_entity.has_component(post_fx_layer_name))
# 9. Delete ExposureControl entity.
exposure_control_entity.delete()
Report.result(Tests.entity_deleted, not exposure_control_entity.exists())
# 10. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, exposure_control_entity.exists())
# 11. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not exposure_control_entity.exists())
# 12. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_ExposureControl_AddedToEntity)
@@ -0,0 +1,164 @@
"""
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
"""
# fmt: off
class Tests:
camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created")
camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity")
camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component")
creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
global_skylight_creation = ("Global Skylight (IBL) Entity successfully created", "Global Skylight (IBL) Entity failed to be created")
global_skylight_component = ("Entity has a Global Skylight (IBL) component", "Entity failed to find Global Skylight (IBL) component")
diffuse_image_set = ("Entity has the Diffuse Image set", "Entity did not the Diffuse Image set")
specular_image_set = ("Entity has the Specular Image set", "Entity did not the Specular Image set")
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")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity():
"""
Summary:
Tests the Global Skylight (IBL) 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 Global Skylight (IBL) entity with no components.
2) Add Global Skylight (IBL) component to Global Skylight (IBL) 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) Add Post FX Layer component.
9) Add Camera component
10) Delete Global Skylight (IBL) entity.
11) UNDO deletion.
12) REDO deletion.
13) Look for errors.
:return: None
"""
import os
import azlmbr.legacy.general as general
import azlmbr.math as math
from editor_python_test_tools.asset_utils import Asset
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a Global Skylight (IBL) entity with no components.
global_skylight_name = "Global Skylight (IBL)"
global_skylight_entity = EditorEntity.create_editor_entity_at(
math.Vector3(512.0, 512.0, 34.0), global_skylight_name)
Report.critical_result(Tests.global_skylight_creation, global_skylight_entity.exists())
# 2. Add Global Skylight (IBL) component to Global Skylight (IBL) entity.
global_skylight_component = global_skylight_entity.add_component(global_skylight_name)
Report.critical_result(
Tests.global_skylight_component, global_skylight_entity.has_component(global_skylight_name))
# 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 global_skylight_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, global_skylight_entity.exists())
# 5. Enter/Exit game mode.
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
helper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
global_skylight_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, global_skylight_entity.is_hidden() is True)
# 7. Test IsVisible.
global_skylight_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, global_skylight_entity.is_visible() is True)
# 8. Set the Diffuse Image asset on the Global Skylight (IBL) entity.
global_skylight_diffuse_image_property = "Controller|Configuration|Diffuse Image"
diffuse_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage")
diffuse_image_asset = Asset.find_asset_by_path(diffuse_image_path, False)
global_skylight_component.set_component_property_value(
global_skylight_diffuse_image_property, diffuse_image_asset.id)
diffuse_image_set = global_skylight_component.get_component_property_value(
global_skylight_diffuse_image_property)
Report.result(Tests.diffuse_image_set, diffuse_image_set == diffuse_image_asset.id)
# 9. Set the Specular Image asset on the Global Light (IBL) entity.
global_skylight_specular_image_property = "Controller|Configuration|Specular Image"
specular_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage")
specular_image_asset = Asset.find_asset_by_path(specular_image_path, False)
global_skylight_component.set_component_property_value(
global_skylight_specular_image_property, specular_image_asset.id)
specular_image_added = global_skylight_component.get_component_property_value(
global_skylight_specular_image_property)
Report.result(Tests.specular_image_set, specular_image_added == specular_image_asset.id)
# 10. Delete Global Skylight (IBL) entity.
global_skylight_entity.delete()
Report.result(Tests.entity_deleted, not global_skylight_entity.exists())
# 11. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, global_skylight_entity.exists())
# 12. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not global_skylight_entity.exists())
# 13. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_GlobalSkylightIBL_AddedToEntity)
@@ -0,0 +1,136 @@
"""
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
"""
# fmt: off
class Tests:
camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created")
camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity")
camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component")
creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
light_creation = ("Light Entity successfully created", "Light Entity failed to be created")
light_component = ("Entity has a Light component", "Entity failed to find Light 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")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_Light_AddedToEntity():
"""
Summary:
Tests the Light 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 Light entity with no components.
2) Add Light component to the Light 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 Light entity.
9) UNDO deletion.
10) REDO deletion.
11) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
import azlmbr.math as math
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a Light entity with no components.
light_name = "Light"
light_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), light_name)
Report.critical_result(Tests.light_creation, light_entity.exists())
# 2. Add Light component to the Light entity.
light_entity.add_component(light_name)
Report.critical_result(Tests.light_component, light_entity.has_component(light_name))
# 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 light_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, light_entity.exists())
# 5. Enter/Exit game mode.
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
helper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
light_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, light_entity.is_hidden() is True)
# 7. Test IsVisible.
light_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, light_entity.is_visible() is True)
# 8. Delete Light entity.
light_entity.delete()
Report.result(Tests.entity_deleted, not light_entity.exists())
# 9. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, light_entity.exists())
# 10. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not light_entity.exists())
# 11. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_Light_AddedToEntity)
@@ -1,10 +1,8 @@
"""
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
Hydra script that creates an entity, attaches the Light component to it for test verifications.
The test verifies that each light type option is available and can be selected without errors.
"""
import os
@@ -0,0 +1,136 @@
"""
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
"""
# fmt: off
class Tests:
camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created")
camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity")
camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component")
creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
physical_sky_creation = ("Physical Sky Entity successfully created", "Physical Sky Entity failed to be created")
physical_sky_component = ("Entity has a Physical Sky component", "Entity failed to find Physical Sky 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")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_PhysicalSky_AddedToEntity():
"""
Summary:
Tests the Physical Sky 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 Physical Sky entity with no components.
2) Add Physical Sky component to Physical Sky 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 Physical Sky entity.
9) UNDO deletion.
10) REDO deletion.
11) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
import azlmbr.math as math
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a Physical Sky entity with no components.
physical_sky_name = "Physical Sky"
physical_sky_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), physical_sky_name)
Report.critical_result(Tests.physical_sky_creation, physical_sky_entity.exists())
# 2. Add Physical Sky component to Physical Sky entity.
physical_sky_entity.add_component(physical_sky_name)
Report.critical_result(Tests.physical_sky_component, physical_sky_entity.has_component(physical_sky_name))
# 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 physical_sky_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, physical_sky_entity.exists())
# 5. Enter/Exit game mode.
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
helper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
physical_sky_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, physical_sky_entity.is_hidden() is True)
# 7. Test IsVisible.
physical_sky_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, physical_sky_entity.is_visible() is True)
# 8. Delete Physical Sky entity.
physical_sky_entity.delete()
Report.result(Tests.entity_deleted, not physical_sky_entity.exists())
# 9. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, physical_sky_entity.exists())
# 10. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not physical_sky_entity.exists())
# 11. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_PhysicalSky_AddedToEntity)
@@ -0,0 +1,138 @@
"""
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
"""
# fmt: off
class Tests:
camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created")
camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity")
camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component")
creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed")
creation_redo = ("REDO Entity creation success", "REDO Entity creation failed")
postfx_radius_weight_creation = ("PostFX Radius Weight Modifier Entity successfully created", "PostFX Radius Weight Modifier Entity failed to be created")
postfx_radius_weight_component = ("Entity has a PostFX Radius Weight Modifier component", "Entity failed to find PostFX Radius Weight Modifier 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")
no_error_occurred = ("No errors detected", "Errors were detected")
# fmt: on
def AtomEditorComponents_PostFXRadiusWeightModifier_AddedToEntity():
"""
Summary:
Tests the PostFX Radius Weight Modifier 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 Post FX Radius Weight Modifier entity with no components.
2) Add Post FX Radius Weight Modifier component to Post FX Radius Weight Modifier 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 PostFX Radius Weight Modifier entity.
9) UNDO deletion.
10) REDO deletion.
11) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
import azlmbr.math as math
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
helper.init_idle()
helper.open_level("", "Base")
# Test steps begin.
# 1. Create a Post FX Radius Weight Modifier entity with no components.
postfx_radius_weight_name = "PostFX Radius Weight Modifier"
postfx_radius_weight_entity = EditorEntity.create_editor_entity_at(
math.Vector3(512.0, 512.0, 34.0), postfx_radius_weight_name)
Report.critical_result(Tests.postfx_radius_weight_creation, postfx_radius_weight_entity.exists())
# 2. Add Post FX Radius Weight Modifier component to Post FX Radius Weight Modifier entity.
postfx_radius_weight_entity.add_component(postfx_radius_weight_name)
Report.critical_result(
Tests.postfx_radius_weight_component, postfx_radius_weight_entity.has_component(postfx_radius_weight_name))
# 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 postfx_radius_weight_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, postfx_radius_weight_entity.exists())
# 5. Enter/Exit game mode.
helper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
helper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
postfx_radius_weight_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, postfx_radius_weight_entity.is_hidden() is True)
# 7. Test IsVisible.
postfx_radius_weight_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, postfx_radius_weight_entity.is_visible() is True)
# 8. Delete PostFX Radius Weight Modifier entity.
postfx_radius_weight_entity.delete()
Report.result(Tests.entity_deleted, not postfx_radius_weight_entity.exists())
# 9. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, postfx_radius_weight_entity.exists())
# 10. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not postfx_radius_weight_entity.exists())
# 11. Look for errors.
helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0)
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_PostFXRadiusWeightModifier_AddedToEntity)
@@ -3,12 +3,12 @@ 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 azlmbr.materialeditor will fail with a ModuleNotFound error when using this script with Editor.exe
This is because azlmbr.materialeditor only binds to MaterialEditor.exe and not Editor.exe
You need to launch this script with MaterialEditor.exe in order for azlmbr.materialeditor to appear.
"""
# import azlmbr.materialeditor will fail with a ModuleNotFound error when using this script with Editor.exe
# This is because azlmbr.materialeditor only binds to MaterialEditor.exe and not Editor.exe
# You need to launch this script with MaterialEditor.exe in order for azlmbr.materialeditor to appear.
import os
import sys
import time
@@ -3,11 +3,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.
SPDX-License-Identifier: Apache-2.0 OR MIT
Hydra script that is used to create a new level with a default rendering setup.
After the level is setup, screenshots are diffed against golden images are used to verify pass/fail results of the test.
See the run() function for more in-depth test info.
"""
import os
@@ -3,11 +3,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.
SPDX-License-Identifier: Apache-2.0 OR MIT
Hydra script that is used to create a new level with a default rendering setup.
After the level is setup, screenshots are diffed against golden images are used to verify pass/fail results of the test.
See the run() function for more in-depth test info.
"""
import os
@@ -3,12 +3,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.
SPDX-License-Identifier: Apache-2.0 OR MIT
Hydra script that is used to create an entity with a Light component attached.
It then updates the property values of the Light component and takes a screenshot.
The screenshot is compared against an expected golden image for test verification.
See the run() function for more in-depth test info.
"""
import os
import sys
@@ -0,0 +1,43 @@
"""
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):
class AtomEditorComponents_DecalAdded(EditorSharedTest):
from atom_renderer.atom_hydra_scripts import hydra_AtomEditorComponents_DecalAdded as test_module
class AtomEditorComponents_DepthOfFieldAdded(EditorSharedTest):
from atom_renderer.atom_hydra_scripts import hydra_AtomEditorComponents_DepthOfFieldAdded as test_module
class AtomEditorComponents_DirectionalLightAdded(EditorSharedTest):
from atom_renderer.atom_hydra_scripts import hydra_AtomEditorComponents_DirectionalLightAdded as test_module
class AtomEditorComponents_ExposureControlAdded(EditorSharedTest):
from atom_renderer.atom_hydra_scripts import hydra_AtomEditorComponents_ExposureControlAdded as test_module
class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest):
from atom_renderer.atom_hydra_scripts import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module
class AtomEditorComponents_PhysicalSkyAdded(EditorSharedTest):
from atom_renderer.atom_hydra_scripts import hydra_AtomEditorComponents_PhysicalSkyAdded as test_module
class AtomEditorComponents_PostFXRadiusWeightModifierAdded(EditorSharedTest):
from atom_renderer.atom_hydra_scripts import (
hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded as test_module)
class AtomEditorComponents_LightAdded(EditorSharedTest):
from atom_renderer.atom_hydra_scripts import hydra_AtomEditorComponents_LightAdded as test_module
class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest):
from atom_renderer.atom_hydra_scripts import hydra_AtomEditorComponents_DisplayMapperAdded as test_module
@@ -35,6 +35,7 @@ class TestDistanceBetweenFilter(object):
@pytest.mark.test_case_id("C4851066")
@pytest.mark.SUITE_periodic
@pytest.mark.dynveg_filter
@pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/4155")
def test_DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius(self, request, editor, level, launcher_platform):
expected_lines = [
@@ -56,6 +57,7 @@ class TestDistanceBetweenFilter(object):
@pytest.mark.test_case_id("C4814458")
@pytest.mark.SUITE_periodic
@pytest.mark.dynveg_filter
@pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/4155")
def test_DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius(self, request, editor, level,
launcher_platform):
+1 -1
View File
@@ -70,7 +70,7 @@ protected:
m_splineEntries.resize(m_splineEntries.size() + 1);
SplineEntry& entry = m_splineEntries.back();
ISplineSet* pSplineSet = (pCtrl ? pCtrl->m_pSplineSet : nullptr);
entry.id = (pSplineSet ? pSplineSet->GetIDFromSpline(pSpline) : nullptr);
entry.id = (pSplineSet ? pSplineSet->GetIDFromSpline(pSpline) : AZStd::string{});
entry.pSpline = pSpline;
const int numKeys = pSpline->GetKeyCount();
+3 -5
View File
@@ -1135,7 +1135,6 @@ bool CCryEditDoc::SaveLevel(const QString& filename)
{
// if we're saving to a new folder, we need to copy the old folder tree.
auto pIPak = GetIEditor()->GetSystem()->GetIPak();
pIPak->Lock();
const QString oldLevelPattern = QDir(oldLevelFolder).absoluteFilePath("*.*");
const QString oldLevelName = Path::GetFile(GetLevelPathName());
@@ -1199,7 +1198,6 @@ bool CCryEditDoc::SaveLevel(const QString& filename)
QFile(filePath).setPermissions(QFile::ReadOther | QFile::WriteOther);
});
pIPak->Unlock();
}
// Save level to XML archive.
@@ -1813,8 +1811,8 @@ bool CCryEditDoc::BackupBeforeSave(bool force)
QString subFolder = theTime.toString("yyyy-MM-dd [HH.mm.ss]");
QString levelName = GetIEditor()->GetGameEngine()->GetLevelName();
QString backupPath = saveBackupPath + "/" + subFolder + "/";
gEnv->pCryPak->MakeDir(backupPath.toUtf8().data());
QString backupPath = saveBackupPath + "/" + subFolder;
AZ::IO::FileIOBase::GetDirectInstance()->CreatePath(backupPath.toUtf8().data());
QString sourcePath = QString::fromUtf8(resolvedLevelPath) + "/";
@@ -2028,7 +2026,7 @@ const char* CCryEditDoc::GetTemporaryLevelName() const
void CCryEditDoc::DeleteTemporaryLevel()
{
QString tempLevelPath = (Path::GetEditingGameDataFolder() + "/Levels/" + GetTemporaryLevelName()).c_str();
GetIEditor()->GetSystem()->GetIPak()->ClosePacks(tempLevelPath.toUtf8().data(), AZ::IO::IArchive::EPathResolutionRules::FLAGS_ADD_TRAILING_SLASH);
GetIEditor()->GetSystem()->GetIPak()->ClosePacks(tempLevelPath.toUtf8().data());
CFileUtil::Deltree(tempLevelPath.toUtf8().data(), true);
}
@@ -5,9 +5,11 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "EditorPreferencesPageViewportGeneral.h"
#include "EditorViewportSettings.h"
#include <AzQtComponents/Components/StyleManager.h>
@@ -15,7 +17,6 @@
#include "DisplaySettings.h"
#include "Settings.h"
void CEditorPreferencesPage_ViewportGeneral::Reflect(AZ::SerializeContext& serialize)
{
serialize.Class<General>()
@@ -23,7 +24,8 @@ void CEditorPreferencesPage_ViewportGeneral::Reflect(AZ::SerializeContext& seria
->Field("Sync2DViews", &General::m_sync2DViews)
->Field("DefaultFOV", &General::m_defaultFOV)
->Field("DefaultAspectRatio", &General::m_defaultAspectRatio)
->Field("EnableContextMenu", &General::m_enableContextMenu);
->Field("EnableContextMenu", &General::m_contextMenuEnabled)
->Field("StickySelect", &General::m_stickySelectEnabled);
serialize.Class<Display>()
->Version(1)
@@ -46,10 +48,12 @@ void CEditorPreferencesPage_ViewportGeneral::Reflect(AZ::SerializeContext& seria
->Field("ShowGridGuide", &Display::m_showGridGuide)
->Field("DisplayDimensions", &Display::m_displayDimension);
// clang-format off
serialize.Class<MapViewport>()
->Version(1)
->Field("SwapXY", &MapViewport::m_swapXY)
->Field("Resolution", &MapViewport::m_resolution);
// clang-format on
serialize.Class<TextLabels>()
->Version(1)
@@ -80,31 +84,51 @@ void CEditorPreferencesPage_ViewportGeneral::Reflect(AZ::SerializeContext& seria
editContext->Class<General>("General Viewport Settings", "")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &General::m_sync2DViews, "Synchronize 2D Viewports", "Synchronize 2D Viewports")
->DataElement(AZ::Edit::UIHandlers::SpinBox, &General::m_defaultFOV, "Perspective View FOV", "Perspective View FOV")
->Attribute("Multiplier", RAD2DEG(1))
->Attribute(AZ::Edit::Attributes::Min, 1.0f)
->Attribute(AZ::Edit::Attributes::Max, 120.0f)
->DataElement(AZ::Edit::UIHandlers::SpinBox, &General::m_defaultAspectRatio, "Perspective View Aspect Ratio", "Perspective View Aspect Ratio")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &General::m_enableContextMenu, "Enable Right-Click Context Menu", "Enable Right-Click Context Menu");
->Attribute("Multiplier", RAD2DEG(1))
->Attribute(AZ::Edit::Attributes::Min, 1.0f)
->Attribute(AZ::Edit::Attributes::Max, 120.0f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &General::m_defaultAspectRatio, "Perspective View Aspect Ratio",
"Perspective View Aspect Ratio")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &General::m_contextMenuEnabled, "Enable Right-Click Context Menu",
"Enable Right-Click Context Menu")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &General::m_stickySelectEnabled, "Enable Sticky Select", "Enable Sticky Select");
editContext->Class<Display>("Viewport Display Settings", "")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_showSafeFrame, "Show 4:3 Aspect Ratio Frame", "Show 4:3 Aspect Ratio Frame")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_highlightSelGeom, "Highlight Selected Geometry", "Highlight Selected Geometry")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_highlightSelVegetation, "Highlight Selected Vegetation", "Highlight Selected Vegetation")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_highlightOnMouseOver, "Highlight Geometry On Mouse Over", "Highlight Geometry On Mouse Over")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_hideMouseCursorWhenCaptured, "Hide Cursor When Captured", "Hide Mouse Cursor When Captured")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &Display::m_showSafeFrame, "Show 4:3 Aspect Ratio Frame", "Show 4:3 Aspect Ratio Frame")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &Display::m_highlightSelGeom, "Highlight Selected Geometry", "Highlight Selected Geometry")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &Display::m_highlightSelVegetation, "Highlight Selected Vegetation",
"Highlight Selected Vegetation")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &Display::m_highlightOnMouseOver, "Highlight Geometry On Mouse Over",
"Highlight Geometry On Mouse Over")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &Display::m_hideMouseCursorWhenCaptured, "Hide Cursor When Captured",
"Hide Mouse Cursor When Captured")
->DataElement(AZ::Edit::UIHandlers::SpinBox, &Display::m_dragSquareSize, "Drag Square Size", "Drag Square Size")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_displayLinks, "Display Object Links", "Display Object Links")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_displayTracks, "Display Animation Tracks", "Display Animation Tracks")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_alwaysShowRadii, "Always Show Radii", "Always Show Radii")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_showBBoxes, "Show Bounding Boxes", "Show Bounding Boxes")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_drawEntityLabels, "Always Draw Entity Labels", "Always Draw Entity Labels")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_showTriggerBounds, "Always Show Trigger Bounds", "Always Show Trigger Bounds")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &Display::m_drawEntityLabels, "Always Draw Entity Labels", "Always Draw Entity Labels")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &Display::m_showTriggerBounds, "Always Show Trigger Bounds", "Always Show Trigger Bounds")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_showIcons, "Show Object Icons", "Show Object Icons")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_distanceScaleIcons, "Scale Object Icons with Distance", "Scale Object Icons with Distance")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_showFrozenHelpers, "Show Helpers of Frozen Objects", "Show Helpers of Frozen Objects")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &Display::m_distanceScaleIcons, "Scale Object Icons with Distance",
"Scale Object Icons with Distance")
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &Display::m_showFrozenHelpers, "Show Helpers of Frozen Objects",
"Show Helpers of Frozen Objects")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_fillSelectedShapes, "Fill Selected Shapes", "Fill Selected Shapes")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_showGridGuide, "Show Snapping Grid Guide", "Show Snapping Grid Guide")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &Display::m_displayDimension, "Display Dimension Figures", "Display Dimension Figures");
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &Display::m_displayDimension, "Display Dimension Figures", "Display Dimension Figures");
editContext->Class<MapViewport>("Map Viewport Settings", "")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &MapViewport::m_swapXY, "Swap X/Y Axis", "Swap X/Y Axis")
@@ -113,42 +137,64 @@ void CEditorPreferencesPage_ViewportGeneral::Reflect(AZ::SerializeContext& seria
editContext->Class<TextLabels>("Text Label Settings", "")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &TextLabels::m_labelsOn, "Enabled", "Enabled")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &TextLabels::m_labelsDistance, "Distance", "Distance")
->Attribute(AZ::Edit::Attributes::Min, 0.f)
->Attribute(AZ::Edit::Attributes::Max, 100000.f);
->Attribute(AZ::Edit::Attributes::Min, 0.f)
->Attribute(AZ::Edit::Attributes::Max, 100000.f);
editContext->Class<SelectionPreviewColor>("Selection Preview Color Settings", "")
->DataElement(AZ::Edit::UIHandlers::Color, &SelectionPreviewColor::m_colorGroupBBox, "Group Bounding Box", "Group Bounding Box")
->DataElement(AZ::Edit::UIHandlers::Color, &SelectionPreviewColor::m_colorEntityBBox, "Entity Bounding Box", "Entity Bounding Box")
->DataElement(AZ::Edit::UIHandlers::SpinBox, &SelectionPreviewColor::m_fBBoxAlpha, "Bounding Box Highlight Alpha", "Bounding Box Highlight Alpha")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->DataElement(
AZ::Edit::UIHandlers::Color, &SelectionPreviewColor::m_colorEntityBBox, "Entity Bounding Box", "Entity Bounding Box")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &SelectionPreviewColor::m_fBBoxAlpha, "Bounding Box Highlight Alpha",
"Bounding Box Highlight Alpha")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->DataElement(AZ::Edit::UIHandlers::Color, &SelectionPreviewColor::m_geometryHighlightColor, "Geometry Color", "Geometry Color")
->DataElement(AZ::Edit::UIHandlers::Color, &SelectionPreviewColor::m_solidBrushGeometryColor, "Solid Brush Geometry Color", "Solid Brush Geometry Color")
->DataElement(AZ::Edit::UIHandlers::SpinBox, &SelectionPreviewColor::m_fgeomAlpha, "Geometry Highlight Alpha", "Geometry Highlight Alpha")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->DataElement(AZ::Edit::UIHandlers::SpinBox, &SelectionPreviewColor::m_childObjectGeomAlpha, "Child Geometry Highlight Alpha", "Child Geometry Highlight Alpha")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f);
->DataElement(
AZ::Edit::UIHandlers::Color, &SelectionPreviewColor::m_solidBrushGeometryColor, "Solid Brush Geometry Color",
"Solid Brush Geometry Color")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &SelectionPreviewColor::m_fgeomAlpha, "Geometry Highlight Alpha", "Geometry Highlight Alpha")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &SelectionPreviewColor::m_childObjectGeomAlpha, "Child Geometry Highlight Alpha",
"Child Geometry Highlight Alpha")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f);
editContext->Class<CEditorPreferencesPage_ViewportGeneral>("General Viewport Preferences", "General Viewport Preferences")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ_CRC("PropertyVisibility_ShowChildrenOnly", 0xef428f20))
->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportGeneral::m_general, "General Viewport Settings", "General Viewport Settings")
->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportGeneral::m_display, "Viewport Display Settings", "Viewport Display Settings")
->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportGeneral::m_map, "Map Viewport Settings", "Map Viewport Settings")
->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportGeneral::m_textLabels, "Text Label Settings", "Text Label Settings")
->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportGeneral::m_selectionPreviewColor, "Selection Preview Color Settings", "Selection Preview Color Settings");
->DataElement(
AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportGeneral::m_general, "General Viewport Settings",
"General Viewport Settings")
->DataElement(
AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportGeneral::m_display, "Viewport Display Settings",
"Viewport Display Settings")
->DataElement(
AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportGeneral::m_map, "Map Viewport Settings",
"Map Viewport Settings")
->DataElement(
AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportGeneral::m_textLabels, "Text Label Settings",
"Text Label Settings")
->DataElement(
AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportGeneral::m_selectionPreviewColor,
"Selection Preview Color Settings", "Selection Preview Color Settings");
}
}
CEditorPreferencesPage_ViewportGeneral::CEditorPreferencesPage_ViewportGeneral()
{
InitializeSettings();
m_icon = QIcon(":/res/Viewport.svg");
}
const char* CEditorPreferencesPage_ViewportGeneral::GetCategory()
{
return "Viewports";
}
const char* CEditorPreferencesPage_ViewportGeneral::GetTitle()
{
return "Viewport";
@@ -159,14 +205,25 @@ QIcon& CEditorPreferencesPage_ViewportGeneral::GetIcon()
return m_icon;
}
void CEditorPreferencesPage_ViewportGeneral::OnCancel()
{
// noop
}
bool CEditorPreferencesPage_ViewportGeneral::OnQueryCancel()
{
return true;
}
void CEditorPreferencesPage_ViewportGeneral::OnApply()
{
CDisplaySettings* ds = GetIEditor()->GetDisplaySettings();
gSettings.viewports.fDefaultAspectRatio = m_general.m_defaultAspectRatio;
gSettings.viewports.fDefaultFov = m_general.m_defaultFOV;
gSettings.viewports.bEnableContextMenu = m_general.m_enableContextMenu;
gSettings.viewports.bEnableContextMenu = m_general.m_contextMenuEnabled;
gSettings.viewports.bSync2DViews = m_general.m_sync2DViews;
SandboxEditor::SetStickySelectEnabled(m_general.m_stickySelectEnabled);
gSettings.viewports.bShowSafeFrame = m_display.m_showSafeFrame;
gSettings.viewports.bHighlightSelectedGeometry = m_display.m_highlightSelGeom;
@@ -202,19 +259,19 @@ void CEditorPreferencesPage_ViewportGeneral::OnApply()
gSettings.objectColorSettings.fChildGeomAlpha = m_selectionPreviewColor.m_childObjectGeomAlpha;
gSettings.objectColorSettings.entityHighlight = QColor(
static_cast<int>(m_selectionPreviewColor.m_colorEntityBBox.GetR() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_colorEntityBBox.GetG() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_colorEntityBBox.GetB() * 255.0f));
static_cast<int>(m_selectionPreviewColor.m_colorEntityBBox.GetR() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_colorEntityBBox.GetG() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_colorEntityBBox.GetB() * 255.0f));
gSettings.objectColorSettings.groupHighlight = QColor(
static_cast<int>(m_selectionPreviewColor.m_colorGroupBBox.GetR() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_colorGroupBBox.GetG() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_colorGroupBBox.GetB() * 255.0f));
static_cast<int>(m_selectionPreviewColor.m_colorGroupBBox.GetR() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_colorGroupBBox.GetG() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_colorGroupBBox.GetB() * 255.0f));
gSettings.objectColorSettings.fBBoxAlpha = m_selectionPreviewColor.m_fBBoxAlpha;
gSettings.objectColorSettings.fGeomAlpha = m_selectionPreviewColor.m_fgeomAlpha;
gSettings.objectColorSettings.geometryHighlightColor = QColor(
static_cast<int>(m_selectionPreviewColor.m_geometryHighlightColor.GetR() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_geometryHighlightColor.GetG() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_geometryHighlightColor.GetB() * 255.0f));
static_cast<int>(m_selectionPreviewColor.m_geometryHighlightColor.GetR() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_geometryHighlightColor.GetG() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_geometryHighlightColor.GetB() * 255.0f));
gSettings.objectColorSettings.solidBrushGeometryColor = QColor(
static_cast<int>(m_selectionPreviewColor.m_solidBrushGeometryColor.GetR() * 255.0f),
static_cast<int>(m_selectionPreviewColor.m_solidBrushGeometryColor.GetG() * 255.0f),
@@ -227,8 +284,9 @@ void CEditorPreferencesPage_ViewportGeneral::InitializeSettings()
m_general.m_defaultAspectRatio = gSettings.viewports.fDefaultAspectRatio;
m_general.m_defaultFOV = gSettings.viewports.fDefaultFov;
m_general.m_enableContextMenu = gSettings.viewports.bEnableContextMenu;
m_general.m_contextMenuEnabled = gSettings.viewports.bEnableContextMenu;
m_general.m_sync2DViews = gSettings.viewports.bSync2DViews;
m_general.m_stickySelectEnabled = SandboxEditor::StickySelectEnabled();
m_display.m_showSafeFrame = gSettings.viewports.bShowSafeFrame;
m_display.m_highlightSelGeom = gSettings.viewports.bHighlightSelectedGeometry;
@@ -256,10 +314,22 @@ void CEditorPreferencesPage_ViewportGeneral::InitializeSettings()
m_textLabels.m_labelsDistance = ds->GetLabelsDistance();
m_selectionPreviewColor.m_childObjectGeomAlpha = gSettings.objectColorSettings.fChildGeomAlpha;
m_selectionPreviewColor.m_colorEntityBBox.Set(static_cast<float>(gSettings.objectColorSettings.entityHighlight.redF()), static_cast<float>(gSettings.objectColorSettings.entityHighlight.greenF()), static_cast<float>(gSettings.objectColorSettings.entityHighlight.blueF()), 1.0f);
m_selectionPreviewColor.m_colorGroupBBox.Set(static_cast<float>(gSettings.objectColorSettings.groupHighlight.redF()), static_cast<float>(gSettings.objectColorSettings.groupHighlight.greenF()), static_cast<float>(gSettings.objectColorSettings.groupHighlight.blueF()), 1.0f);
m_selectionPreviewColor.m_colorEntityBBox.Set(
static_cast<float>(gSettings.objectColorSettings.entityHighlight.redF()),
static_cast<float>(gSettings.objectColorSettings.entityHighlight.greenF()),
static_cast<float>(gSettings.objectColorSettings.entityHighlight.blueF()), 1.0f);
m_selectionPreviewColor.m_colorGroupBBox.Set(
static_cast<float>(gSettings.objectColorSettings.groupHighlight.redF()),
static_cast<float>(gSettings.objectColorSettings.groupHighlight.greenF()),
static_cast<float>(gSettings.objectColorSettings.groupHighlight.blueF()), 1.0f);
m_selectionPreviewColor.m_fBBoxAlpha = gSettings.objectColorSettings.fBBoxAlpha;
m_selectionPreviewColor.m_fgeomAlpha = gSettings.objectColorSettings.fGeomAlpha;
m_selectionPreviewColor.m_geometryHighlightColor.Set(static_cast<float>(gSettings.objectColorSettings.geometryHighlightColor.redF()), static_cast<float>(gSettings.objectColorSettings.geometryHighlightColor.greenF()), static_cast<float>(gSettings.objectColorSettings.geometryHighlightColor.blueF()), 1.0f);
m_selectionPreviewColor.m_solidBrushGeometryColor.Set(static_cast<float>(gSettings.objectColorSettings.solidBrushGeometryColor.redF()), static_cast<float>(gSettings.objectColorSettings.solidBrushGeometryColor.greenF()), static_cast<float>(gSettings.objectColorSettings.solidBrushGeometryColor.blueF()), 1.0f);
m_selectionPreviewColor.m_geometryHighlightColor.Set(
static_cast<float>(gSettings.objectColorSettings.geometryHighlightColor.redF()),
static_cast<float>(gSettings.objectColorSettings.geometryHighlightColor.greenF()),
static_cast<float>(gSettings.objectColorSettings.geometryHighlightColor.blueF()), 1.0f);
m_selectionPreviewColor.m_solidBrushGeometryColor.Set(
static_cast<float>(gSettings.objectColorSettings.solidBrushGeometryColor.redF()),
static_cast<float>(gSettings.objectColorSettings.solidBrushGeometryColor.greenF()),
static_cast<float>(gSettings.objectColorSettings.solidBrushGeometryColor.blueF()), 1.0f);
}
@@ -5,18 +5,17 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include "Include/IPreferencesPage.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Math/Color.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <QIcon>
class CEditorPreferencesPage_ViewportGeneral
: public IPreferencesPage
class CEditorPreferencesPage_ViewportGeneral : public IPreferencesPage
{
public:
AZ_RTTI(CEditorPreferencesPage_ViewportGeneral, "{8511FF7F-F774-47E1-A99B-3DE3A867E403}", IPreferencesPage)
@@ -26,12 +25,12 @@ public:
CEditorPreferencesPage_ViewportGeneral();
virtual ~CEditorPreferencesPage_ViewportGeneral() = default;
virtual const char* GetCategory() override { return "Viewports"; }
virtual const char* GetCategory() override;
virtual const char* GetTitle() override;
virtual QIcon& GetIcon() override;
virtual void OnApply() override;
virtual void OnCancel() override {}
virtual bool OnQueryCancel() override { return true; }
virtual void OnCancel() override;
virtual bool OnQueryCancel() override;
private:
void InitializeSettings();
@@ -43,7 +42,8 @@ private:
bool m_sync2DViews;
float m_defaultFOV;
float m_defaultAspectRatio;
bool m_enableContextMenu;
bool m_contextMenuEnabled;
bool m_stickySelectEnabled;
};
struct Display
@@ -106,5 +106,3 @@ private:
SelectionPreviewColor m_selectionPreviewColor;
QIcon m_icon;
};
+11
View File
@@ -20,6 +20,7 @@ namespace SandboxEditor
constexpr AZStd::string_view AngleSnappingSetting = "/Amazon/Preferences/Editor/AngleSnapping";
constexpr AZStd::string_view AngleSizeSetting = "/Amazon/Preferences/Editor/AngleSize";
constexpr AZStd::string_view ShowGridSetting = "/Amazon/Preferences/Editor/ShowGrid";
constexpr AZStd::string_view StickySelectSetting = "/Amazon/Preferences/Editor/StickySelect";
constexpr AZStd::string_view ManipulatorLineBoundWidthSetting = "/Amazon/Preferences/Editor/Manipulator/LineBoundWidth";
constexpr AZStd::string_view ManipulatorCircleBoundWidthSetting = "/Amazon/Preferences/Editor/Manipulator/CircleBoundWidth";
constexpr AZStd::string_view CameraTranslateSpeedSetting = "/Amazon/Preferences/Editor/Camera/TranslateSpeed";
@@ -158,6 +159,16 @@ namespace SandboxEditor
SetRegistry(ShowGridSetting, showing);
}
bool StickySelectEnabled()
{
return GetRegistry(StickySelectSetting, false);
}
void SetStickySelectEnabled(const bool enabled)
{
SetRegistry(StickySelectSetting, enabled);
}
float ManipulatorLineBoundWidth()
{
return aznumeric_cast<float>(GetRegistry(ManipulatorLineBoundWidthSetting, 0.1));
+3
View File
@@ -47,6 +47,9 @@ namespace SandboxEditor
SANDBOX_API bool ShowingGrid();
SANDBOX_API void SetShowingGrid(bool showing);
SANDBOX_API bool StickySelectEnabled();
SANDBOX_API void SetStickySelectEnabled(bool enabled);
SANDBOX_API float ManipulatorLineBoundWidth();
SANDBOX_API void SetManipulatorLineBoundWidth(float lineBoundWidth);
+5
View File
@@ -2522,6 +2522,11 @@ float EditorViewportSettings::ManipulatorCircleBoundWidth() const
return SandboxEditor::ManipulatorCircleBoundWidth();
}
bool EditorViewportSettings::StickySelectEnabled() const
{
return SandboxEditor::StickySelectEnabled();
}
AZ_CVAR_EXTERNED(bool, ed_previewGameInFullscreen_once);
bool EditorViewportWidget::ShouldPreviewFullscreen() const
+1
View File
@@ -77,6 +77,7 @@ struct EditorViewportSettings : public AzToolsFramework::ViewportInteraction::Vi
float AngleStep() const override;
float ManipulatorLineBoundWidth() const override;
float ManipulatorCircleBoundWidth() const override;
bool StickySelectEnabled() const override;
};
// EditorViewportWidget window
-19
View File
@@ -432,25 +432,6 @@ void CGameExporter::ExportFileList(const QString& path, const QString& levelName
newFileNode->setAttr("src", handle.m_filename.data());
newFileNode->setAttr("dest", handle.m_filename.data());
newFileNode->setAttr("size", handle.m_fileDesc.nSize);
unsigned char md5[16];
AZStd::string filenameToHash = GetIEditor()->GetGameEngine()->GetLevelPath().toUtf8().data();
filenameToHash += "/";
filenameToHash += AZStd::string{ handle.m_filename.data(), handle.m_filename.size() };
if (gEnv->pCryPak->ComputeMD5(filenameToHash.data(), md5))
{
char md5string[33];
sprintf_s(md5string, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
md5[0], md5[1], md5[2], md5[3],
md5[4], md5[5], md5[6], md5[7],
md5[8], md5[9], md5[10], md5[11],
md5[12], md5[13], md5[14], md5[15]);
newFileNode->setAttr("md5", md5string);
}
else
{
newFileNode->setAttr("md5", "");
}
}
}
} while (handle = gEnv->pCryPak->FindNext(handle));
@@ -6,6 +6,7 @@
*
*/
#include <AzCore/PlatformIncl.h>
#include "CryFile.h"
#include "PerforceSourceControl.h"
#include "PasswordDlg.h"
+2 -1
View File
@@ -8,6 +8,7 @@
*/
#pragma once
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/any.h>
namespace AzToolsFramework
@@ -138,7 +139,7 @@ namespace AzToolsFramework
/*
* Finds a pak file name for a given file.
*/
virtual const char* GetPakFromFile(const char* filename) = 0;
virtual AZ::IO::Path GetPakFromFile(const char* filename) = 0;
/*
* Prints the message to the editor console window.
+5 -4
View File
@@ -625,7 +625,7 @@ namespace
}
//////////////////////////////////////////////////////////////////////////
const char* PyGetPakFromFile(const char* filename)
AZ::IO::Path PyGetPakFromFile(const char* filename)
{
auto pIPak = GetIEditor()->GetSystem()->GetIPak();
AZ::IO::HandleType fileHandle = pIPak->FOpen(filename, "rb");
@@ -633,8 +633,9 @@ namespace
{
throw std::logic_error("Invalid file name.");
}
const char* pArchPath = pIPak->GetFileArchivePath(fileHandle);
AZ::IO::Path pArchPath = pIPak->GetFileArchivePath(fileHandle);
pIPak->FClose(fileHandle);
return pArchPath;
}
@@ -1040,7 +1041,7 @@ namespace AzToolsFramework
return PySetAxisConstraint(pConstrain);
}
const char* PythonEditorComponent::GetPakFromFile(const char* filename)
AZ::IO::Path PythonEditorComponent::GetPakFromFile(const char* filename)
{
return PyGetPakFromFile(filename);
}
@@ -1114,7 +1115,7 @@ namespace AzToolsFramework
addLegacyGeneral(behaviorContext->Method("get_axis_constraint", PyGetAxisConstraint, nullptr, "Gets axis."));
addLegacyGeneral(behaviorContext->Method("set_axis_constraint", PySetAxisConstraint, nullptr, "Sets axis."));
addLegacyGeneral(behaviorContext->Method("get_pak_from_file", PyGetPakFromFile, nullptr, "Finds a pak file name for a given file."));
addLegacyGeneral(behaviorContext->Method("get_pak_from_file", [](const char* filename) -> AZStd::string { return PyGetPakFromFile(filename).Native(); }, nullptr, "Finds a pak file name for a given file."));
addLegacyGeneral(behaviorContext->Method("log", PyLog, nullptr, "Prints the message to the editor console window."));
+1 -1
View File
@@ -91,7 +91,7 @@ namespace AzToolsFramework
void SetAxisConstraint(AZStd::string_view pConstrain) override;
const char* GetPakFromFile(const char* filename) override;
AZ::IO::Path GetPakFromFile(const char* filename) override;
void Log(const char* pMessage) override;
+1 -2
View File
@@ -171,7 +171,6 @@ SEditorSettings::SEditorSettings()
bBackupOnSave = true;
backupOnSaveMaxCount = 3;
bApplyConfigSpecInEditor = true;
useLowercasePaths = 0;
showErrorDialogOnLoad = 1;
consoleBackgroundColorTheme = AzToolsFramework::ConsoleColorTheme::Dark;
@@ -887,6 +886,7 @@ void SEditorSettings::Load()
//////////////////////////////////////////////////////////////////////////
AZ_CVAR(bool, ed_previewGameInFullscreen_once, false, nullptr, AZ::ConsoleFunctorFlags::IsInvisible, "Preview the game (Ctrl+G, \"Play Game\", etc.) in fullscreen once");
AZ_CVAR(bool, ed_lowercasepaths, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Convert CCryFile paths to lowercase on Open");
void SEditorSettings::PostInitApply()
{
@@ -898,7 +898,6 @@ void SEditorSettings::PostInitApply()
// Create CVars.
REGISTER_CVAR2("ed_highlightGeometry", &viewports.bHighlightMouseOverGeometry, viewports.bHighlightMouseOverGeometry, 0, "Highlight geometry when mouse over it");
REGISTER_CVAR2("ed_showFrozenHelpers", &viewports.nShowFrozenHelpers, viewports.nShowFrozenHelpers, 0, "Show helpers of frozen objects");
REGISTER_CVAR2("ed_lowercasepaths", &useLowercasePaths, useLowercasePaths, 0, "generate paths in lowercase");
gEnv->pConsole->RegisterInt("fe_fbx_savetempfile", 0, 0, "When importing an FBX file into Facial Editor, this will save out a conversion FSQ to the Animations/temp folder for trouble shooting");
REGISTER_CVAR2_CB("ed_toolbarIconSize", &gui.nToolbarIconSize, gui.nToolbarIconSize, VF_NULL, "Override size of the toolbar icons 0-default, 16,32,...", ToolbarIconSizeChanged);
-2
View File
@@ -340,8 +340,6 @@ AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
//! how many save backups to keep
int backupOnSaveMaxCount;
int useLowercasePaths;
//////////////////////////////////////////////////////////////////////////
// Autobackup.
//////////////////////////////////////////////////////////////////////////
+18 -11
View File
@@ -149,12 +149,13 @@ bool CFileUtil::ExtractFile(QString& file, bool bMsgBoxAskForExtraction, const c
// Check if in pack.
if (cryfile.IsInPak())
{
const char* sPakName = cryfile.GetPakPath();
if (bMsgBoxAskForExtraction)
{
AZ::IO::FixedMaxPath sPakName{ cryfile.GetPakPath() };
// Cannot edit file in pack, suggest to extract it for editing.
if (QMessageBox::critical(QApplication::activeWindow(), QString(), QObject::tr("File %1 is inside a PAK file %2\r\nDo you want it to be extracted for editing ?").arg(file, sPakName), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
if (QMessageBox::critical(QApplication::activeWindow(), QString(),
QObject::tr("File %1 is inside a PAK file %2\r\nDo you want it to be extracted for editing ?").arg(file, sPakName.c_str()),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
{
return false;
}
@@ -173,10 +174,9 @@ bool CFileUtil::ExtractFile(QString& file, bool bMsgBoxAskForExtraction, const c
if (diskFile.open(QFile::WriteOnly))
{
// Copy data from packed file to disk file.
char* data = new char[cryfile.GetLength()];
cryfile.ReadRaw(data, cryfile.GetLength());
diskFile.write(data, cryfile.GetLength());
delete []data;
auto data = AZStd::make_unique<char[]>(cryfile.GetLength());
cryfile.ReadRaw(data.get(), cryfile.GetLength());
diskFile.write(data.get(), cryfile.GetLength());
}
else
{
@@ -185,7 +185,14 @@ bool CFileUtil::ExtractFile(QString& file, bool bMsgBoxAskForExtraction, const c
}
else
{
file = cryfile.GetAdjustedFilename();
if (auto fileIoBase = AZ::IO::FileIOBase::GetInstance(); fileIoBase != nullptr)
{
if (AZ::IO::FixedMaxPath resolvedFilePath; fileIoBase->ResolvePath(resolvedFilePath, cryfile.GetFilename()))
{
file = QString::fromUtf8(resolvedFilePath.c_str(), static_cast<int>(resolvedFilePath.Native().size()));
}
}
}
return true;
@@ -2157,13 +2164,13 @@ uint32 CFileUtil::GetAttributes(const char* filename, bool bUseSourceControl /*=
return SCC_FILE_ATTRIBUTE_READONLY | SCC_FILE_ATTRIBUTE_INPAK;
}
const char* adjustedFile = file.GetAdjustedFilename();
if (!AZ::IO::SystemFile::Exists(adjustedFile))
auto fileIoBase = AZ::IO::FileIOBase::GetInstance();
if (!fileIoBase->Exists(file.GetFilename()))
{
return SCC_FILE_ATTRIBUTE_INVALID;
}
if (!AZ::IO::SystemFile::IsWritable(adjustedFile))
if (fileIoBase->IsReadOnly(file.GetFilename()))
{
return SCC_FILE_ATTRIBUTE_NORMAL | SCC_FILE_ATTRIBUTE_READONLY;
}
+2 -2
View File
@@ -68,7 +68,7 @@ bool CPakFile::Open(const char* filename, bool bAbsolutePath)
if (bAbsolutePath)
{
m_pArchive = pCryPak->OpenArchive(filename, nullptr, AZ::IO::INestedArchive::FLAGS_ABSOLUTE_PATHS);
m_pArchive = pCryPak->OpenArchive(filename, {}, AZ::IO::INestedArchive::FLAGS_ABSOLUTE_PATHS);
}
else
{
@@ -93,7 +93,7 @@ bool CPakFile::OpenForRead(const char* filename)
{
return false;
}
m_pArchive = pCryPak->OpenArchive(filename, nullptr, AZ::IO::INestedArchive::FLAGS_OPTIMIZED_READ_ONLY | AZ::IO::INestedArchive::FLAGS_ABSOLUTE_PATHS);
m_pArchive = pCryPak->OpenArchive(filename, {}, AZ::IO::INestedArchive::FLAGS_OPTIMIZED_READ_ONLY | AZ::IO::INestedArchive::FLAGS_ABSOLUTE_PATHS);
if (m_pArchive)
{
return true;
+1 -1
View File
@@ -124,7 +124,7 @@ bool CXmlArchive::SaveToPak([[maybe_unused]] const QString& levelPath, CPakFile&
if (pakFile.GetArchive())
{
CLogFile::FormatLine("Saving pak file %s", (const char*)pakFile.GetArchive()->GetFullPath());
CLogFile::FormatLine("Saving pak file %.*s", AZ_STRING_ARG(pakFile.GetArchive()->GetFullPath().Native()));
}
pNamedData->Save(pakFile);
+2
View File
@@ -154,6 +154,8 @@ void CViewportTitleDlg::SetupCameraDropdownMenu()
cameraMenu->addMenu(GetFovMenu());
m_ui->m_cameraMenu->setMenu(cameraMenu);
m_ui->m_cameraMenu->setPopupMode(QToolButton::InstantPopup);
QObject::connect(cameraMenu, &QMenu::aboutToShow, this, &CViewportTitleDlg::CheckForCameraSpeedUpdate);
QAction* gotoPositionAction = new QAction("Go to position", cameraMenu);
connect(gotoPositionAction, &QAction::triggered, this, &CViewportTitleDlg::OnBnClickedGotoPosition);
cameraMenu->addAction(gotoPositionAction);
+1
View File
@@ -1717,6 +1717,7 @@ AZ_POP_DISABLE_WARNING
{
EBusRouterNode<typename EBus::InterfaceType> m_routerNode;
public:
virtual ~EBusNestedVersionRouter() = default;
template<class Container>
void BusRouterConnect(Container& container, int order = 0);
@@ -370,6 +370,11 @@ namespace AZ
//! @param applyPatchSettings The ApplyPatchSettings which are using during JSON Merging
virtual void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings) = 0;
virtual void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings) = 0;
//! Stores option to indicate whether the FileIOBase instance should be used for file operations
//! @param useFileIo If true the FileIOBase instance will attempted to be used for FileIOBase
//! operations before falling back to use SystemFile
virtual void SetUseFileIO(bool useFileIo) = 0;
};
inline SettingsRegistryInterface::Visitor::~Visitor() = default;
@@ -9,11 +9,14 @@
#include <cctype>
#include <cerrno>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/JSON/error/en.h>
#include <AzCore/NativeUI//NativeUIRequests.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/StackedString.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/sort.h>
#include <AzCore/std/parallel/scoped_lock.h>
@@ -131,6 +134,12 @@ namespace AZ
pointer.Create(m_settings, m_settings.GetAllocator()).SetArray();
}
SettingsRegistryImpl::SettingsRegistryImpl(bool useFileIo)
: SettingsRegistryImpl()
{
m_useFileIo = useFileIo;
}
void SettingsRegistryImpl::SetContext(SerializeContext* context)
{
AZStd::scoped_lock lock(m_settingMutex);
@@ -723,15 +732,10 @@ namespace AZ
RegistryFileList fileList;
scratchBuffer->clear();
AZ::IO::FixedMaxPathString folderPath{ path };
constexpr AZStd::string_view pathSeparators{ AZ_CORRECT_AND_WRONG_DATABASE_SEPARATOR };
if (pathSeparators.find_first_of(folderPath.back()) == AZStd::string_view::npos)
{
folderPath.push_back(AZ_CORRECT_DATABASE_SEPARATOR);
}
AZ::IO::FixedMaxPath folderPath{ path };
const size_t platformKeyOffset = folderPath.size();
folderPath.push_back('*');
const size_t platformKeyOffset = folderPath.Native().size();
folderPath /= '*';
Value specialzationArray(kArrayType);
size_t specializationCount = specializations.GetCount();
@@ -741,47 +745,13 @@ namespace AZ
specialzationArray.PushBack(Value(name.data(), aznumeric_caster(name.length()), m_settings.GetAllocator()), m_settings.GetAllocator());
}
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Folder"), Value(folderPath.c_str(), aznumeric_caster(folderPath.size()), m_settings.GetAllocator()), m_settings.GetAllocator())
.AddMember(StringRef("Folder"), Value(folderPath.c_str(), aznumeric_caster(folderPath.Native().size()), m_settings.GetAllocator()), m_settings.GetAllocator())
.AddMember(StringRef("Specializations"), AZStd::move(specialzationArray), m_settings.GetAllocator());
auto callback = [this, &fileList, &specializations, &pointer, &folderPath](const char* filename, bool isFile) -> bool
auto CreateSettingsFindCallback = [this, &fileList, &specializations, &pointer, &folderPath](bool isPlatformFile)
{
if (isFile)
{
if (fileList.size() >= MaxRegistryFolderEntries)
{
AZ_Error("Settings Registry", false, "Too many files in registry folder.");
AZStd::scoped_lock lock(m_settingMutex);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Too many files in registry folder."), m_settings.GetAllocator())
.AddMember(StringRef("Path"), Value(folderPath.c_str(), aznumeric_caster(folderPath.size()), m_settings.GetAllocator()), m_settings.GetAllocator())
.AddMember(StringRef("File"), Value(filename, m_settings.GetAllocator()), m_settings.GetAllocator());
return false;
}
fileList.push_back();
RegistryFile& registryFile = fileList.back();
if (!ExtractFileDescription(registryFile, filename, specializations))
{
fileList.pop_back();
}
}
return true;
};
SystemFile::FindFiles(folderPath.c_str(), callback);
if (!platform.empty())
{
// Move the folderPath prefix back to the supplied path before the wildcard
folderPath.erase(platformKeyOffset);
folderPath += PlatformFolder;
folderPath.push_back(AZ_CORRECT_DATABASE_SEPARATOR);
folderPath += platform;
folderPath.push_back(AZ_CORRECT_DATABASE_SEPARATOR);
folderPath.push_back('*');
auto platformCallback = [this, &fileList, &specializations, &pointer, &folderPath](const char* filename, bool isFile) -> bool
return [this, &fileList, &specializations, &pointer, &folderPath, isPlatformFile](AZStd::string_view filename, bool isFile) -> bool
{
if (isFile)
{
@@ -791,8 +761,8 @@ namespace AZ
AZStd::scoped_lock lock(m_settingMutex);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Too many files in registry folder."), m_settings.GetAllocator())
.AddMember(StringRef("Path"), Value(folderPath.c_str(), aznumeric_caster(folderPath.size()), m_settings.GetAllocator()), m_settings.GetAllocator())
.AddMember(StringRef("File"), Value(filename, m_settings.GetAllocator()), m_settings.GetAllocator());
.AddMember(StringRef("Path"), Value(folderPath.c_str(), aznumeric_caster(folderPath.Native().size()), m_settings.GetAllocator()), m_settings.GetAllocator())
.AddMember(StringRef("File"), Value(filename.data(), aznumeric_caster(filename.size()), m_settings.GetAllocator()), m_settings.GetAllocator());
return false;
}
@@ -800,7 +770,7 @@ namespace AZ
RegistryFile& registryFile = fileList.back();
if (ExtractFileDescription(registryFile, filename, specializations))
{
registryFile.m_isPlatformFile = true;
registryFile.m_isPlatformFile = isPlatformFile;
}
else
{
@@ -809,7 +779,42 @@ namespace AZ
}
return true;
};
SystemFile::FindFiles(folderPath.c_str(), platformCallback);
};
struct FindFilesPayload
{
bool m_isPlatformFile{};
AZStd::fixed_vector<AZStd::string_view, 2> m_pathSegmentsToAppend;
};
AZStd::fixed_vector<FindFilesPayload, 2> findFilesPayloads{ {false} };
if (!platform.empty())
{
findFilesPayloads.push_back(FindFilesPayload{ true, { PlatformFolder, platform } });
}
for (const FindFilesPayload& findFilesPayload : findFilesPayloads)
{
// Erase back to initial path
folderPath.Native().erase(platformKeyOffset);
for (AZStd::string_view pathSegmentToAppend : findFilesPayload.m_pathSegmentsToAppend)
{
folderPath /= pathSegmentToAppend;
}
auto findFilesCallback = CreateSettingsFindCallback(findFilesPayload.m_isPlatformFile);
if (AZ::IO::FileIOBase* fileIo = m_useFileIo ? AZ::IO::FileIOBase::GetInstance() : nullptr; fileIo != nullptr)
{
auto FileIoToSystemFileFindFiles = [findFilesCallback = AZStd::move(findFilesCallback), fileIo](const char* filePath) -> bool
{
return findFilesCallback(AZ::IO::PathView(filePath).Filename().Native(), !fileIo->IsDirectory(filePath));
};
fileIo->FindFiles(folderPath.c_str(), "*", FileIoToSystemFileFindFiles);
}
else
{
SystemFile::FindFiles((folderPath / "*").c_str(), findFilesCallback);
}
}
if (!fileList.empty())
@@ -831,16 +836,14 @@ namespace AZ
// Load the registry files in the sorted order.
for (RegistryFile& registryFile : fileList)
{
folderPath.erase(platformKeyOffset); // Erase all characters after the platformKeyOffset
folderPath.Native().erase(platformKeyOffset); // Erase all characters after the platformKeyOffset
if (registryFile.m_isPlatformFile)
{
folderPath += PlatformFolder;
folderPath.push_back(AZ_CORRECT_DATABASE_SEPARATOR);
folderPath += platform;
folderPath.push_back(AZ_CORRECT_DATABASE_SEPARATOR);
folderPath /= PlatformFolder;
folderPath /= platform;
}
folderPath += registryFile.m_relativePath;
folderPath /= registryFile.m_relativePath;
if (!registryFile.m_isPatch)
{
@@ -1027,39 +1030,44 @@ namespace AZ
return false;
}
bool SettingsRegistryImpl::ExtractFileDescription(RegistryFile& output, const char* filename, const Specializations& specializations)
bool SettingsRegistryImpl::ExtractFileDescription(RegistryFile& output, AZStd::string_view filename, const Specializations& specializations)
{
if (!filename || filename[0] == 0)
static constexpr auto PatchExtensionWithDot = AZStd::fixed_string<32>(".") + PatchExtension;
static constexpr auto ExtensionWithDot = AZStd::fixed_string<32>(".") + Extension;
static constexpr AZ::IO::PathView PatchExtensionView(PatchExtensionWithDot);
static constexpr AZ::IO::PathView ExtensionView(ExtensionWithDot);
if (filename.empty())
{
AZ_Error("Settings Registry", false, "Settings file without name found");
return false;
}
AZStd::string_view filePath{ filename };
const size_t filePathSize = filePath.size();
AZ::IO::PathView filePath{ filename };
const size_t filePathSize = filePath.Native().size();
// The filePath.empty() check makes sure that the file extension after the final <dot> isn't added to the output.m_tags
AZStd::optional<AZStd::string_view> pathTag = AZ::StringFunc::TokenizeNext(filePath, '.');
for (; pathTag && !filePath.empty(); pathTag = AZ::StringFunc::TokenizeNext(filePath, '.'))
auto AppendSpecTags = [&output](AZStd::string_view pathTag)
{
output.m_tags.push_back(Specializations::Hash(*pathTag));
}
output.m_tags.push_back(Specializations::Hash(pathTag));
};
AZ::StringFunc::TokenizeVisitor(filePath.Stem().Native(), AppendSpecTags, '.');
// If token is invalid, then the filename has no <dot> characters and therefore no extension
if (pathTag)
if (AZ::IO::PathView fileExtension = filePath.Extension(); !fileExtension.empty())
{
if (pathTag->size() >= AZStd::char_traits<char>::length(PatchExtension) && azstrnicmp(pathTag->data(), PatchExtension, pathTag->size()) == 0)
if (fileExtension == PatchExtensionView)
{
output.m_isPatch = true;
}
else if (pathTag->size() != AZStd::char_traits<char>::length(Extension) || azstrnicmp(pathTag->data(), Extension, pathTag->size()) != 0)
else if (fileExtension != ExtensionView)
{
return false;
}
}
else
{
AZ_Error("Settings Registry", false, R"(Settings file without extension found: "%s")", filename);
AZ_Error("Settings Registry", false, R"(Settings file without extension found: "%.*s")", AZ_STRING_ARG(filename));
return false;
}
@@ -1074,7 +1082,7 @@ namespace AZ
{
if (*currentIt == *(currentIt - 1))
{
AZ_Error("Settings Registry", false, R"(One or more tags are duplicated in registry file "%s")", filename);
AZ_Error("Settings Registry", false, R"(One or more tags are duplicated in registry file "%.*s")", AZ_STRING_ARG(filename));
return false;
}
++currentIt;
@@ -1103,11 +1111,123 @@ namespace AZ
}
else
{
AZ_Error("Settings Registry", false, R"(Found relative path to settings file "%s" is too long.)", filename);
AZ_Error("Settings Registry", false, R"(Found relative path to settings file "%.*s" is too long.)", AZ_STRING_ARG(filename));
return false;
}
}
//! Structure which encapsulates Commands to either the FileIOBase or SystemFile classes based on
//! the SettingsRegistry option to use FileIO
struct SettingsRegistryFileReader
{
using FileHandleType = AZStd::variant<AZStd::monostate, AZ::IO::SystemFile, AZ::IO::HandleType>;
SettingsRegistryFileReader() = default;
SettingsRegistryFileReader(bool useFileIo, const char* filePath)
{
Open(useFileIo, filePath);
}
~SettingsRegistryFileReader()
{
if (auto fileHandle = AZStd::get_if<AZ::IO::HandleType>(&m_file); fileHandle != nullptr)
{
if (AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance(); fileIo != nullptr)
{
fileIo->Close(*fileHandle);
}
}
}
bool Open(bool useFileIo, const char* filePath)
{
Close();
if (AZ::IO::FileIOBase* fileIo = useFileIo ? AZ::IO::FileIOBase::GetInstance() : nullptr; fileIo != nullptr)
{
AZ::IO::HandleType fileHandle;
if (fileIo->Open(filePath, IO::OpenMode::ModeRead, fileHandle))
{
m_file = fileHandle;
return true;
}
}
else
{
AZ::IO::SystemFile file;
if (file.Open(filePath, IO::SystemFile::OpenMode::SF_OPEN_READ_ONLY))
{
m_file = AZStd::move(file);
return true;
}
}
return false;
}
bool IsOpen() const
{
if (auto fileHandle = AZStd::get_if<AZ::IO::HandleType>(&m_file); fileHandle != nullptr)
{
return *fileHandle != AZ::IO::InvalidHandle;
}
else if (auto systemFile = AZStd::get_if<AZ::IO::SystemFile>(&m_file); systemFile != nullptr)
{
return systemFile->IsOpen();
}
return false;
}
void Close()
{
if (auto fileHandle = AZStd::get_if<AZ::IO::HandleType>(&m_file); fileHandle != nullptr)
{
if (AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance(); fileIo != nullptr)
{
fileIo->Close(*fileHandle);
}
}
m_file = AZStd::monostate{};
}
u64 Length() const
{
if (auto fileHandle = AZStd::get_if<AZ::IO::HandleType>(&m_file); fileHandle != nullptr)
{
if (u64 fileSize{}; AZ::IO::FileIOBase::GetInstance()->Size(*fileHandle, fileSize))
{
return fileSize;
}
}
else if (auto systemFile = AZStd::get_if<AZ::IO::SystemFile>(&m_file); systemFile != nullptr)
{
return systemFile->Length();
}
return 0;
}
AZ::IO::SizeType Read(AZ::IO::SizeType byteSize, void* buffer)
{
if (auto fileHandle = AZStd::get_if<AZ::IO::HandleType>(&m_file); fileHandle != nullptr)
{
if (AZ::u64 bytesRead{}; AZ::IO::FileIOBase::GetInstance()->Read(*fileHandle, buffer, byteSize, false, &bytesRead))
{
return bytesRead;
}
}
else if (auto systemFile = AZStd::get_if<AZ::IO::SystemFile>(&m_file); systemFile != nullptr)
{
return systemFile->Read(byteSize, buffer);
}
return 0;
}
FileHandleType m_file;
};
bool SettingsRegistryImpl::MergeSettingsFileInternal(const char* path, Format format, AZStd::string_view rootKey,
AZStd::vector<char>& scratchBuffer)
{
@@ -1116,8 +1236,8 @@ namespace AZ
Pointer pointer(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/-");
SystemFile file;
if (!file.Open(path, SystemFile::OpenMode::SF_OPEN_READ_ONLY))
SettingsRegistryFileReader fileReader(m_useFileIo, path);
if (!fileReader.IsOpen())
{
AZ_Error("Settings Registry", false, R"(Unable to open registry file "%s".)", path);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
@@ -1126,7 +1246,7 @@ namespace AZ
return false;
}
u64 fileSize = file.Length();
u64 fileSize = fileReader.Length();
if (fileSize == 0)
{
AZ_Warning("Settings Registry", false, R"(Registry file "%s" is 0 bytes in length. There is no nothing to merge)", path);
@@ -1136,9 +1256,10 @@ namespace AZ
.AddMember(StringRef("Path"), Value(path, m_settings.GetAllocator()), m_settings.GetAllocator());
return false;
}
scratchBuffer.clear();
scratchBuffer.resize_no_construct(fileSize + 1);
if (file.Read(fileSize, scratchBuffer.data()) != fileSize)
if (fileReader.Read(fileSize, scratchBuffer.data()) != fileSize)
{
AZ_Error("Settings Registry", false, R"(Unable to read registry file "%s".)", path);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
@@ -1268,4 +1389,9 @@ namespace AZ
{
applyPatchSettings = m_applyPatchSettings;
}
void SettingsRegistryImpl::SetUseFileIO(bool useFileIo)
{
m_useFileIo = useFileIo;
}
} // namespace AZ
@@ -35,6 +35,10 @@ namespace AZ
static constexpr size_t MaxRegistryFolderEntries = 128;
SettingsRegistryImpl();
//! @param useFileIo - If true attempt to redirect
//! file read operations through the FileIOBase instance first before falling back to SystemFile
//! otherwise always use SystemFile
explicit SettingsRegistryImpl(bool useFileIo);
AZ_DISABLE_COPY_MOVE(SettingsRegistryImpl);
~SettingsRegistryImpl() override = default;
@@ -83,6 +87,8 @@ namespace AZ
void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings) override;
void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings) override;
void SetUseFileIO(bool useFileIo) override;
private:
using TagList = AZStd::fixed_vector<size_t, Specializations::MaxCount + 1>;
struct RegistryFile
@@ -104,7 +110,7 @@ namespace AZ
// Compares if lhs is less than rhs in terms of processing order. This can also detect and report conflicts.
bool IsLessThan(bool& collisionFound, const RegistryFile& lhs, const RegistryFile& rhs, const Specializations& specializations,
const rapidjson::Pointer& historyPointer, AZStd::string_view folderPath);
bool ExtractFileDescription(RegistryFile& output, const char* filename, const Specializations& specializations);
bool ExtractFileDescription(RegistryFile& output, AZStd::string_view filename, const Specializations& specializations);
bool MergeSettingsFileInternal(const char* path, Format format, AZStd::string_view rootKey, AZStd::vector<char>& scratchBuffer);
void SignalNotifier(AZStd::string_view jsonPath, Type type);
@@ -119,5 +125,7 @@ namespace AZ
JsonSerializerSettings m_serializationSettings;
JsonDeserializerSettings m_deserializationSettings;
JsonApplyPatchSettings m_applyPatchSettings;
bool m_useFileIo{};
};
} // namespace AZ
@@ -57,6 +57,7 @@ namespace AZ
MOCK_METHOD1(SetApplyPatchSettings, void(const JsonApplyPatchSettings&));
MOCK_METHOD1(GetApplyPatchSettings, void(JsonApplyPatchSettings&));
MOCK_METHOD1(SetUseFileIO, void(bool));
};
} // namespace AZ
@@ -13,6 +13,7 @@
// the intention is that you only include the customized version of rapidXML through this header, so that
// you can override behavior here.
#include <stdio.h>
#include <rapidxml/rapidxml.h>
#endif // AZCORE_RAPIDXML_RAPIDXML_H_INCLUDED
@@ -96,6 +96,10 @@ namespace AZStd
&& !is_convertible_v<const T&, const Element*>>>
constexpr basic_fixed_string(const T& convertibleToView, size_type rhsOffset, size_type count);
// #12
constexpr basic_fixed_string(AZStd::nullptr_t) = delete;
constexpr operator AZStd::basic_string_view<Element, Traits>() const;
constexpr auto begin() -> iterator;
@@ -120,6 +124,7 @@ namespace AZStd
constexpr auto operator=(const T& convertible_to_view)
-> AZStd::enable_if_t<is_convertible_v<const T&, basic_string_view<Element, Traits>>
&& !is_convertible_v<const T&, const Element*>, basic_fixed_string&>;
constexpr auto operator=(AZStd::nullptr_t) -> basic_fixed_string& = delete;
constexpr auto operator+=(const basic_fixed_string& rhs) -> basic_fixed_string&;
constexpr auto operator+=(const_pointer ptr) -> basic_fixed_string&;
@@ -168,6 +168,9 @@ namespace AZStd
{
}
// C++23 overload to prevent initializing a string_view via a nullptr or integer type
constexpr basic_string(AZStd::nullptr_t) = delete;
inline ~basic_string()
{
// destroy the string
@@ -197,6 +200,7 @@ namespace AZStd
inline this_type& operator=(AZStd::basic_string_view<Element, Traits> view) { return assign(view); }
inline this_type& operator=(const_pointer ptr) { return assign(ptr); }
inline this_type& operator=(Element ch) { return assign(1, ch); }
inline this_type& operator=(AZStd::nullptr_t) = delete;
inline this_type& operator+=(const this_type& rhs) { return append(rhs); }
inline this_type& operator+=(const_pointer ptr) { return append(ptr); }
inline this_type& operator+=(Element ch) { return append(1, ch); }
@@ -502,6 +502,9 @@ namespace AZStd
swap(other);
}
// C++23 overload to prevent initializing a string_view via a nullptr or integer type
constexpr basic_string_view(AZStd::nullptr_t) = delete;
constexpr const_reference operator[](size_type index) const { return data()[index]; }
/// Returns value, not reference. If index is out of bounds, 0 is returned (can't be reference).
constexpr value_type at(size_type index) const
+1 -15
View File
@@ -1210,9 +1210,6 @@ namespace UnitTest
AZStd::string findStr("Hay");
string_view view3(findStr);
string_view nullptrView4(nullptr);
EXPECT_EQ(emptyView1, nullptrView4);
// copy
const size_t destBufferSize = 32;
@@ -1264,9 +1261,6 @@ namespace UnitTest
AZStd::size_t rfindResult = view3.rfind('a', 2);
EXPECT_EQ(1, rfindResult);
rfindResult = nullptrView4.rfind("");
EXPECT_EQ(string_view::npos, rfindResult);
rfindResult = emptyView1.rfind("");
EXPECT_EQ(string_view::npos, rfindResult);
@@ -1373,17 +1367,11 @@ namespace UnitTest
{
string_view view1("The quick brown fox jumped over the lazy dog");
string_view view2("Needle in Haystack");
string_view nullBeaverView(nullptr);
string_view emptyBeaverView;
string_view superEmptyBeaverView("");
EXPECT_EQ(nullBeaverView, emptyBeaverView);
EXPECT_EQ(superEmptyBeaverView, nullBeaverView);
EXPECT_EQ(emptyBeaverView, superEmptyBeaverView);
EXPECT_EQ(nullBeaverView, "");
EXPECT_EQ(nullBeaverView, nullptr);
EXPECT_EQ("", emptyBeaverView);
EXPECT_EQ(nullptr, superEmptyBeaverView);
EXPECT_EQ("", superEmptyBeaverView);
EXPECT_EQ("The quick brown fox jumped over the lazy dog", view1);
EXPECT_NE("The slow brown fox jumped over the lazy dog", view1);
@@ -1421,8 +1409,6 @@ namespace UnitTest
EXPECT_LE(beaverView, "Busy Beaver");
EXPECT_LE("Likable Beaver", notBeaverView);
EXPECT_LE("Busy Beaver", beaverView);
EXPECT_LE(nullBeaverView, nullBeaverView);
EXPECT_LE(nullBeaverView, lowerBeaverStr);
EXPECT_LE(microBeaverStr, view1);
EXPECT_LE(compareStr, beaverView);
@@ -362,7 +362,7 @@ namespace UnitTest
// Test specific construction case that was failing.
// The constructor calls Name::SetName() which does a move assignment
// Name& Name::operator=(Name&& rhs) was leaving m_view pointing to the m_data in a temporary Name object.
AZ::Name emptyName(AZStd::string_view(nullptr));
AZ::Name emptyName(AZStd::string_view{});
EXPECT_TRUE(emptyName.IsEmpty());
EXPECT_EQ(0, emptyName.GetStringView().data()[0]);
}
@@ -1517,7 +1517,7 @@ namespace SettingsRegistryTests
m_testFolder->push_back(AZ_CORRECT_DATABASE_SEPARATOR);
*m_testFolder += AZ::SettingsRegistryInterface::RegistryFolder;
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, {}, nullptr);
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, {});
EXPECT_TRUE(result);
EXPECT_EQ(4, counter);
@@ -1559,7 +1559,7 @@ namespace SettingsRegistryTests
m_testFolder->push_back(AZ_CORRECT_DATABASE_SEPARATOR);
*m_testFolder += AZ::SettingsRegistryInterface::RegistryFolder;
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, "Special", nullptr);
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, "Special");
EXPECT_TRUE(result);
EXPECT_EQ(6, counter);
@@ -1598,7 +1598,7 @@ namespace SettingsRegistryTests
m_testFolder->push_back(AZ_CORRECT_DATABASE_SEPARATOR);
*m_testFolder += AZ::SettingsRegistryInterface::RegistryFolder;
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, {}, nullptr);
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, {});
EXPECT_TRUE(result);
EXPECT_EQ(4, counter);
@@ -1639,7 +1639,7 @@ namespace SettingsRegistryTests
m_testFolder->push_back(AZ_CORRECT_DATABASE_SEPARATOR);
*m_testFolder += AZ::SettingsRegistryInterface::RegistryFolder;
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, {}, nullptr);
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, {});
EXPECT_TRUE(result);
EXPECT_EQ(4, counter);
@@ -1672,7 +1672,7 @@ namespace SettingsRegistryTests
m_testFolder->push_back(AZ_CORRECT_DATABASE_SEPARATOR);
*m_testFolder += AZ::SettingsRegistryInterface::RegistryFolder;
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, "Special", nullptr);
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, "Special");
EXPECT_TRUE(result);
EXPECT_EQ(1, counter);
@@ -1722,7 +1722,7 @@ namespace SettingsRegistryTests
TEST_F(SettingsRegistryTest, MergeSettingsFolder_EmptyFolder_ReportsSuccessButNothingAdded)
{
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, {}, nullptr);
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, {});
EXPECT_TRUE(result);
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Object, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/0")); // Folder and specialization settings.
@@ -1734,7 +1734,7 @@ namespace SettingsRegistryTests
constexpr AZStd::fixed_string<AZ::IO::MaxPathLength + 1> path(AZ::IO::MaxPathLength + 1, 'a');
AZ_TEST_START_TRACE_SUPPRESSION;
bool result = m_registry->MergeSettingsFolder(path, { "editor", "test" }, {}, nullptr);
bool result = m_registry->MergeSettingsFolder(path, { "editor", "test" }, {});
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
EXPECT_FALSE(result);
@@ -1751,7 +1751,7 @@ namespace SettingsRegistryTests
AZ_TEST_START_TRACE_SUPPRESSION;
m_testFolder->push_back(AZ_CORRECT_DATABASE_SEPARATOR);
*m_testFolder += AZ::SettingsRegistryInterface::RegistryFolder;
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, {}, nullptr);
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, {});
EXPECT_GT(::UnitTest::TestRunner::Instance().StopAssertTests(), 0);
EXPECT_FALSE(result);
@@ -81,71 +81,6 @@ namespace AzFramework
static constexpr const char s_prefabSystemKey[] = "/Amazon/Preferences/EnablePrefabSystem";
static constexpr const char s_prefabWipSystemKey[] = "/Amazon/Preferences/EnablePrefabSystemWipFeatures";
static constexpr const char s_legacySlicesAssertKey[] = "/Amazon/Preferences/ShouldAssertForLegacySlicesUsage";
// A Helper function that can load an app descriptor from file.
AZ::Outcome<AZStd::unique_ptr<AZ::ComponentApplication::Descriptor>, AZStd::string> LoadDescriptorFromFilePath(const char* appDescriptorFilePath, AZ::SerializeContext& serializeContext)
{
AZStd::unique_ptr<AZ::ComponentApplication::Descriptor> loadedDescriptor;
AZ::IO::SystemFile appDescriptorFile;
if (!appDescriptorFile.Open(appDescriptorFilePath, AZ::IO::SystemFile::SF_OPEN_READ_ONLY))
{
return AZ::Failure(AZStd::string::format("Failed to open file: %s", appDescriptorFilePath));
}
AZ::IO::SystemFileStream appDescriptorFileStream(&appDescriptorFile, true);
if (!appDescriptorFileStream.IsOpen())
{
return AZ::Failure(AZStd::string::format("Failed to stream file: %s", appDescriptorFilePath));
}
// Callback function for allocating the root elements in the file.
AZ::ObjectStream::InplaceLoadRootInfoCB inplaceLoadCb =
[](void** rootAddress, const AZ::SerializeContext::ClassData**, const AZ::Uuid& classId, AZ::SerializeContext*)
{
if (rootAddress && classId == azrtti_typeid<AZ::ComponentApplication::Descriptor>())
{
// ComponentApplication::Descriptor is normally a singleton.
// Force a unique instance to be created.
*rootAddress = aznew AZ::ComponentApplication::Descriptor();
}
};
// Callback function for saving the root elements in the file.
AZ::ObjectStream::ClassReadyCB classReadyCb =
[&loadedDescriptor](void* classPtr, const AZ::Uuid& classId, AZ::SerializeContext* context)
{
// Save descriptor, delete anything else loaded from file.
if (classId == azrtti_typeid<AZ::ComponentApplication::Descriptor>())
{
loadedDescriptor.reset(static_cast<AZ::ComponentApplication::Descriptor*>(classPtr));
}
else if (const AZ::SerializeContext::ClassData* classData = context->FindClassData(classId))
{
classData->m_factory->Destroy(classPtr);
}
else
{
AZ_Error("Application", false, "Unexpected type %s found in application descriptor file. This memory will leak.",
classId.ToString<AZStd::string>().c_str());
}
};
// There's other stuff in the file we may not recognize (system components), but we're not interested in that stuff.
AZ::ObjectStream::FilterDescriptor loadFilter(&AZ::Data::AssetFilterNoAssetLoading, AZ::ObjectStream::FILTERFLAG_IGNORE_UNKNOWN_CLASSES);
if (!AZ::ObjectStream::LoadBlocking(&appDescriptorFileStream, serializeContext, classReadyCb, loadFilter, inplaceLoadCb))
{
return AZ::Failure(AZStd::string::format("Failed to load objects from file: %s", appDescriptorFilePath));
}
if (!loadedDescriptor)
{
return AZ::Failure(AZStd::string::format("Failed to find descriptor object in file: %s", appDescriptorFilePath));
}
return AZ::Success(AZStd::move(loadedDescriptor));
}
}
Application::Application()
File diff suppressed because it is too large Load Diff
@@ -19,6 +19,7 @@
#include <AzCore/IO/CompressionBus.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/parallel/lock.h>
#include <AzCore/std/parallel/thread.h>
@@ -26,7 +27,6 @@
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/std/string/osstring.h>
#include <AzFramework/Archive/IArchive.h>
#include <AzFramework/Archive/ZipDirCache.h>
@@ -115,12 +115,12 @@ namespace AZ::IO
struct PackDesc
{
AZ::IO::Path m_pathBindRoot; // the zip binding root
AZStd::string strFileName; // the zip file name (with path) - very useful for debugging so please don't remove
AZ::IO::Path m_strFileName; // the zip file name (with path) - very useful for debugging so please don't remove
// [LYN-2376] Remove once legacy slice support is removed
bool m_containsLevelPak = false; // indicates whether this archive has level.pak inside it or not
const char* GetFullPath() const { return pZip->GetFilePath(); }
AZ::IO::PathView GetFullPath() const { return pZip->GetFilePath(); }
AZStd::intrusive_ptr<INestedArchive> pArchive;
ZipDir::CachePtr pZip;
@@ -129,10 +129,7 @@ namespace AZ::IO
// ArchiveFindDataSet entire purpose is to keep a reference to the intrusive_ptr of ArchiveFindData
// so that it doesn't go out of scope
using ArchiveFindDataSet = AZStd::set<AZStd::intrusive_ptr<AZ::IO::FindData>, AZ::OSStdAllocator>;
// given the source relative path, constructs the full path to the file according to the flags
const char* AdjustFileName(AZStd::string_view src, char* dst, size_t dstSize, uint32_t nFlags, bool skipMods = false) override;
using ArchiveFindDataSet = AZStd::set<AZStd::intrusive_ptr<AZ::IO::FindData>>;
/**
@@ -154,29 +151,17 @@ namespace AZ::IO
//! CompressionBus Handler implementation.
void FindCompressionInfo(bool& found, AZ::IO::CompressionInfo& info, const AZStd::string_view filename) override;
//! Processes an alias command line containing multiple aliases.
void ParseAliases(AZStd::string_view szCommandLine) override;
//! adds or removes an alias from the list - if bAdd set to false will remove it
void SetAlias(AZStd::string_view szName, AZStd::string_view szAlias, bool bAdd) override;
//! gets an alias from the list, if any exist.
//! if bReturnSame==true, it will return the input name if an alias doesn't exist. Otherwise returns nullptr
const char* GetAlias(AZStd::string_view szName, bool bReturnSame = true) override;
// Set the localization folder
void SetLocalizationFolder(AZStd::string_view sLocalizationFolder) override;
const char* GetLocalizationFolder() const override { return m_sLocalizationFolder.c_str(); }
const char* GetLocalizationRoot() const override { return m_sLocalizationRoot.c_str(); }
// lock all the operations
void Lock() override;
void Unlock() override;
// open the physical archive file - creates if it doesn't exist
// returns nullptr if it's invalid or can't open the file
AZStd::intrusive_ptr<INestedArchive> OpenArchive(AZStd::string_view szPath, AZStd::string_view bindRoot = {}, uint32_t nFlags = 0, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr) override;
AZStd::intrusive_ptr<INestedArchive> OpenArchive(AZStd::string_view szPath, AZStd::string_view bindRoot = {}, uint32_t nArchiveFlags = 0, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr) override;
// returns the path to the archive in which the file was opened
const char* GetFileArchivePath(AZ::IO::HandleType fileHandle) override;
AZ::IO::PathView GetFileArchivePath(AZ::IO::HandleType fileHandle) override;
//////////////////////////////////////////////////////////////////////////
@@ -192,40 +177,31 @@ namespace AZ::IO
void RegisterFileAccessSink(IArchiveFileAccessSink* pSink) override;
void UnregisterFileAccessSink(IArchiveFileAccessSink* pSink) override;
bool Init(AZStd::string_view szBasePath) override;
void Release() override;
bool IsInstalledToHDD(AZStd::string_view acFilePath = 0) const override;
// [LYN-2376] Remove 'addLevels' parameter once legacy slice support is removed
bool OpenPack(AZStd::string_view pName, uint32_t nFlags = 0, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr, AZ::IO::FixedMaxPathString* pFullPath = nullptr, bool addLevels = true) override;
bool OpenPack(AZStd::string_view szBindRoot, AZStd::string_view pName, uint32_t nFlags = 0, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr, AZ::IO::FixedMaxPathString* pFullPath = nullptr, bool addLevels = true) override;
bool OpenPack(AZStd::string_view pName, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr, AZ::IO::FixedMaxPathString* pFullPath = nullptr, bool addLevels = true) override;
bool OpenPack(AZStd::string_view szBindRoot, AZStd::string_view pName, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr, AZ::IO::FixedMaxPathString* pFullPath = nullptr, bool addLevels = true) override;
// after this call, the file will be unlocked and closed, and its contents won't be used to search for files
bool ClosePack(AZStd::string_view pName, uint32_t nFlags = 0) override;
bool OpenPacks(AZStd::string_view pWildcard, uint32_t nFlags = 0, AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr) override;
bool OpenPacks(AZStd::string_view szBindRoot, AZStd::string_view pWildcard, uint32_t nFlags = 0, AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr) override;
bool ClosePack(AZStd::string_view pName) override;
bool OpenPacks(AZStd::string_view pWildcard, AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr) override;
bool OpenPacks(AZStd::string_view szBindRoot, AZStd::string_view pWildcard, AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr) override;
// closes pack files by the path and wildcard
bool ClosePacks(AZStd::string_view pWildcard, uint32_t nFlags = 0) override;
bool ClosePacks(AZStd::string_view pWildcard) override;
//returns if a archive exists matching the wildcard
bool FindPacks(AZStd::string_view pWildcardIn) override;
// prevent access to specific archive files
bool SetPacksAccessible(bool bAccessible, AZStd::string_view pWildcard, uint32_t nFlags = 0) override;
bool SetPackAccessible(bool bAccessible, AZStd::string_view pName, uint32_t nFlags = 0) override;
bool SetPacksAccessible(bool bAccessible, AZStd::string_view pWildcard) override;
bool SetPackAccessible(bool bAccessible, AZStd::string_view pName) override;
// returns the file modification time
uint64_t GetModificationTime(AZ::IO::HandleType fileHandle) override;
bool LoadPakToMemory(AZStd::string_view pName, EInMemoryArchiveLocation nLoadArchiveToMemory, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pMemoryBlock = nullptr) override;
void LoadPaksToMemory(int nMaxArchiveSize, bool bLoadToMemory) override;
AZ::IO::HandleType FOpen(AZStd::string_view pName, const char* mode, uint32_t nPathFlags = 0) override;
size_t FReadRaw(void* data, size_t length, size_t elems, AZ::IO::HandleType handle) override;
size_t FReadRawAll(void* data, size_t nFileSize, AZ::IO::HandleType handle) override;
AZ::IO::HandleType FOpen(AZStd::string_view pName, const char* mode) override;
size_t FRead(void* data, size_t bytesToRead, AZ::IO::HandleType handle) override;
void* FGetCachedFileData(AZ::IO::HandleType handle, size_t& nFileSize) override;
size_t FWrite(const void* data, size_t length, size_t elems, AZ::IO::HandleType handle) override;
size_t FWrite(const void* data, size_t bytesToWrite, AZ::IO::HandleType handle) override;
size_t FSeek(AZ::IO::HandleType handle, uint64_t seek, int mode) override;
uint64_t FTell(AZ::IO::HandleType handle) override;
int FFlush(AZ::IO::HandleType handle) override;
@@ -234,9 +210,7 @@ namespace AZ::IO
AZ::IO::ArchiveFileIterator FindNext(AZ::IO::ArchiveFileIterator fileIterator) override;
bool FindClose(AZ::IO::ArchiveFileIterator fileIterator) override;
int FEof(AZ::IO::HandleType handle) override;
char* FGets(char*, int, AZ::IO::HandleType) override;
int Getc(AZ::IO::HandleType) override;
int FPrintf(AZ::IO::HandleType handle, const char* format, ...) override;
size_t FGetSize(AZ::IO::HandleType fileHandle) override;
size_t FGetSize(AZStd::string_view sFilename, bool bAllowUseFileSystem = false) override;
bool IsInPak(AZ::IO::HandleType handle) override;
@@ -248,9 +222,6 @@ namespace AZ::IO
bool IsFolder(AZStd::string_view sPath) override;
IArchive::SignedFileSize GetFileSizeOnDisk(AZStd::string_view filename) override;
// creates a directory
bool MakeDir(AZStd::string_view szPath) override;
// compresses the raw data into raw data. The buffer for compressed data itself with the heap passed. Uses method 8 (deflate)
// returns one of the Z_* errors (Z_OK upon success)
// MT-safe
@@ -275,22 +246,12 @@ namespace AZ::IO
IResourceList* GetResourceList(ERecordFileOpenList eList) override;
void SetResourceList(ERecordFileOpenList eList, IResourceList* pResourceList) override;
uint32_t ComputeCRC(AZStd::string_view szPath, uint32_t nFileOpenFlags = 0) override;
bool ComputeMD5(AZStd::string_view szPath, uint8_t* md5, uint32_t nFileOpenFlags = 0, bool useDirectFileAccess = false) override;
void DisableRuntimeFileAccess(bool status) override
{
m_disableRuntimeFileAccess[0] = status;
m_disableRuntimeFileAccess[1] = status;
m_disableRuntimeFileAccess = status;
}
bool DisableRuntimeFileAccess(bool status, AZStd::thread_id threadId) override;
bool CheckFileAccessDisabled(AZStd::string_view name, const char* mode) override;
void SetRenderThreadId(AZStd::thread_id renderThreadId) override
{
m_renderThreadId = renderThreadId;
}
// gets the current archive priority
ArchiveLocationPriority GetPakPriority() const override;
@@ -307,11 +268,11 @@ namespace AZ::IO
// Return cached file data for entries inside archive file.
CCachedFileDataPtr GetOpenedFileDataInZip(AZ::IO::HandleType file);
ZipDir::FileEntry* FindPakFileEntry(AZStd::string_view szPath, uint32_t& nArchiveFlags,
ZipDir::CachePtr* pZip = {}, bool bSkipInMemoryArchives = {}) const;
ZipDir::CachePtr* pZip = {}) const;
private:
bool OpenPackCommon(AZStd::string_view szBindRoot, AZStd::string_view pName, uint32_t nArchiveFlags, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr, bool addLevels = true);
bool OpenPacksCommon(AZStd::string_view szDir, AZStd::string_view pWildcardIn, uint32_t nArchiveFlags, AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr, bool addLevels = true);
bool OpenPackCommon(AZStd::string_view szBindRoot, AZStd::string_view pName, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr, bool addLevels = true);
bool OpenPacksCommon(AZStd::string_view szDir, AZStd::string_view pWildcardIn, AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr, bool addLevels = true);
ZipDir::FileEntry* FindPakFileEntry(AZStd::string_view szPath) const;
@@ -346,9 +307,6 @@ namespace AZ::IO
AZStd::mutex m_cachedFileRawDataMutex;
// For m_pCachedFileRawDataSet
using RawDataCacheLockGuard = AZStd::scoped_lock<decltype(m_cachedFileRawDataMutex)>;
// The F* emulation functions critical section: protects all F* functions
// that don't have a chance to be called recursively (to avoid deadlocks)
AZStd::mutex m_csMain;
mutable AZStd::shared_mutex m_archiveMutex;
ArchiveArray m_arrArchives;
@@ -360,8 +318,6 @@ namespace AZ::IO
//////////////////////////////////////////////////////////////////////////
IArchive::ERecordFileOpenList m_eRecordFileOpenList = RFOM_Disabled;
using RecordedFilesSet = AZStd::set<AZ::OSString, AZ::IO::AZStdStringLessCaseInsensitive, AZ::OSStdAllocator>;
RecordedFilesSet m_recordedFilesSet;
AZStd::intrusive_ptr<IResourceList> m_pEngineStartupResourceList;
@@ -372,28 +328,16 @@ namespace AZ::IO
float m_fFileAccessTime{}; // Time used to perform file operations
AZStd::vector<IArchiveFileAccessSink*, AZ::OSStdAllocator> m_FileAccessSinks; // useful for gathering file access statistics
bool m_disableRuntimeFileAccess[2]{};
bool m_disableRuntimeFileAccess{};
//threads which we don't want to access files from during the game
AZStd::thread_id m_mainThreadId{};
AZStd::thread_id m_renderThreadId{};
AZStd::fixed_string<128> m_sLocalizationFolder;
AZStd::fixed_string<128> m_sLocalizationRoot;
AZStd::set<uint32_t, AZStd::less<>, AZ::OSStdAllocator> m_filesCachedOnHDD;
// [LYN-2376] Remove once legacy slice support is removed
LevelPackOpenEvent m_levelOpenEvent;
LevelPackCloseEvent m_levelCloseEvent;
};
}
namespace AZ::IO::ArchiveInternal
{
// Utility function to de-alias archive file opening and file-within-archive opening
// if the file specified was an absolute path but it points at one of the aliases, de-alias it and replace it with that alias.
// this works around problems where the level editor is in control but still mounts asset packs (ie, level.pak mounted as @assets@)
AZStd::optional<AZ::IO::FixedMaxPath> ConvertAbsolutePathToAliasedPath(AZStd::string_view sourcePath,
AZStd::string_view aliasToLookFor = "@devassets@", AZStd::string_view aliasToReplaceWith = "@assets@");
}
@@ -5,10 +5,9 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/parallel/lock.h>
#include <AzCore/std/functional.h> // for function<> in the find files callback.
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/Archive/ArchiveFileIO.h>
#include <AzFramework/Archive/IArchive.h>
@@ -188,7 +187,7 @@ namespace AZ::IO
return IO::ResultCode::Error;
}
size_t result = m_archive->FReadRaw(buffer, 1, size, fileHandle);
size_t result = m_archive->FRead(buffer, size, fileHandle);
if (bytesRead)
{
*bytesRead = static_cast<AZ::u64>(result);
@@ -213,7 +212,7 @@ namespace AZ::IO
return IO::ResultCode::Error;
}
size_t result = m_archive->FWrite(buffer, 1, size, fileHandle);
size_t result = m_archive->FWrite(buffer, size, fileHandle);
if (bytesWritten)
{
*bytesWritten = static_cast<AZ::u64>(result);
@@ -357,14 +356,8 @@ namespace AZ::IO
return IO::ResultCode::Error;
}
// avoid using AZStd::string if possible - use OSString instead of StringFunc
AZ::OSString destPath(destinationFilePath);
IO::Path destPath(IO::PathView(destinationFilePath).ParentPath());
AZ::OSString::size_type pos = destPath.find_last_of(AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR);
if (pos != AZ::OSString::npos)
{
destPath.resize(pos);
}
CreatePath(destPath.c_str());
if (!Open(destinationFilePath, IO::OpenMode::ModeWrite | IO::OpenMode::ModeBinary, destinationFile))
@@ -466,31 +459,25 @@ namespace AZ::IO
return IO::ResultCode::Error;
}
AZStd::fixed_string<AZ_MAX_PATH_LEN> total = filePath;
AZ::IO::FixedMaxPath total = filePath;
if (total.empty())
{
return IO::ResultCode::Error;
}
if (!total.ends_with(AZ_CORRECT_FILESYSTEM_SEPARATOR) && !total.ends_with(AZ_WRONG_FILESYSTEM_SEPARATOR))
{
total.append(AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
}
total.append(filter);
total /= filter;
AZ::IO::ArchiveFileIterator fileIterator = m_archive->FindFirst(total.c_str());
if (!fileIterator)
{
return IO::ResultCode::Success; // its not an actual fatal error to not find anything.
}
for (;fileIterator; fileIterator = m_archive->FindNext(fileIterator))
for (; fileIterator; fileIterator = m_archive->FindNext(fileIterator))
{
total = AZStd::fixed_string<AZ_MAX_PATH_LEN>::format("%s/%.*s", filePath, aznumeric_cast<int>(fileIterator.m_filename.size()), fileIterator.m_filename.data());
AZStd::optional resolvedAliasLength = ConvertToAlias(total.data(), total.capacity());
if (resolvedAliasLength)
total = filePath;
total /= fileIterator.m_filename;
if (ConvertToAlias(total, total))
{
total.resize_no_construct(*resolvedAliasLength);
if (!callback(total.c_str()))
{
break;
@@ -510,8 +497,13 @@ namespace AZ::IO
const auto fileIt = m_trackedFiles.find(fileHandle);
if (fileIt != m_trackedFiles.end())
{
AZ_Assert(filenameSize >= fileIt->second.length(), "Filename size %" PRIu64 " is larger than the size of the tracked file %s:%zu", fileIt->second.c_str(), fileIt->second.size());
azstrncpy(filename, filenameSize, fileIt->second.c_str(), fileIt->second.length());
const AZStd::string_view trackedFileView = fileIt->second.Native();
if (filenameSize <= trackedFileView.size())
{
return false;
}
size_t trackedFileViewLength = trackedFileView.copy(filename, trackedFileView.size());
filename[trackedFileViewLength] = '\0';
return true;
}
@@ -13,7 +13,6 @@
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/fixed_vector.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/string/osstring.h>
namespace AZ::IO
@@ -78,7 +77,7 @@ namespace AZ::IO
protected:
// we keep a list of file names ever opened so that we can easily return it.
mutable AZStd::recursive_mutex m_operationGuard;
AZStd::unordered_map<IO::HandleType, AZ::OSString, AZStd::hash<IO::HandleType>, AZStd::equal_to<IO::HandleType>, AZ::OSStdAllocator> m_trackedFiles;
AZStd::unordered_map<IO::HandleType, AZ::IO::Path> m_trackedFiles;
AZStd::fixed_vector<char, ArchiveFileIoMaxBuffersize> m_copyBuffer;
IArchive* m_archive;
};
@@ -15,34 +15,6 @@
namespace AZ::IO
{
size_t ArchiveFileIteratorHash::operator()(const AZ::IO::ArchiveFileIterator& iter) const
{
return iter.GetHash();
}
bool AZStdStringLessCaseInsensitive::operator()(AZStd::string_view left, AZStd::string_view right) const
{
// If one or both strings are 0-length, return true if the left side is smaller, false if they're equal or left is larger.
size_t compareLength = (AZStd::min)(left.size(), right.size());
if (compareLength == 0)
{
return left.size() < right.size();
}
// They're both non-zero, so compare the strings up until the length of the shorter string.
int compareResult = azstrnicmp(left.data(), right.data(), compareLength);
// If both strings are equal for the number of characters compared, return true if the left side is shorter, false if
// they're equal or left is longer.
if (compareResult == 0)
{
return left.size() < right.size();
}
// Return true if the left side should come first alphabetically, false if the right side should.
return compareResult < 0;
}
FileDesc::FileDesc(Attribute fileAttribute, uint64_t fileSize, time_t accessTime, time_t creationTime, time_t writeTime)
: nAttrib{ fileAttribute }
, nSize{ fileSize }
@@ -52,10 +24,9 @@ namespace AZ::IO
{
}
ArchiveFileIterator::ArchiveFileIterator(FindData* findData, AZStd::string_view filename, const FileDesc& fileDesc)
// ArchiveFileIterator
ArchiveFileIterator::ArchiveFileIterator(FindData* findData)
: m_findData{ findData }
, m_filename{ filename }
, m_fileDesc{ fileDesc }
{
}
@@ -73,21 +44,36 @@ namespace AZ::IO
return operator++();
}
bool ArchiveFileIterator::operator==(const AZ::IO::ArchiveFileIterator& rhs) const
{
return GetHash() == rhs.GetHash();
}
ArchiveFileIterator::operator bool() const
{
return m_findData && m_lastFetchValid;
}
size_t ArchiveFileIterator::GetHash() const
// FindData::ArchiveFile
FindData::ArchiveFile::ArchiveFile() = default;
FindData::ArchiveFile::ArchiveFile(AZStd::string_view filename, const FileDesc& fileDesc)
: m_filename(filename)
, m_fileDesc(fileDesc)
{
}
size_t FindData::ArchiveFile::GetHash() const
{
return AZStd::hash<AZ::IO::PathView>{}(m_filename.c_str());
}
bool FindData::ArchiveFile::operator==(const ArchiveFile& rhs) const
{
return GetHash() == rhs.GetHash();
}
// FindData::ArchiveFilehash
size_t FindData::ArchiveFileHash::operator()(const ArchiveFile& archiveFile) const
{
return archiveFile.GetHash();
}
// FindData
void FindData::Scan(IArchive* archive, AZStd::string_view szDir, bool bAllowUseFS, bool bScanZips)
{
// get the priority into local variable to avoid it changing in the course of
@@ -119,40 +105,37 @@ namespace AZ::IO
void FindData::ScanFS([[maybe_unused]] IArchive* archive, AZStd::string_view szDirIn)
{
AZStd::string searchDirectory;
AZStd::string pattern;
AZ::IO::PathView directory{ szDirIn };
AZ::IO::FixedMaxPath searchDirectory = directory.ParentPath();
AZ::IO::FixedMaxPath pattern = directory.Filename();
auto ScanFileSystem = [this](const char* filePath) -> bool
{
AZ::IO::PathString directory{ szDirIn };
AZ::StringFunc::Path::GetFullPath(directory.c_str(), searchDirectory);
AZ::StringFunc::Path::GetFullFileName(directory.c_str(), pattern);
}
AZ::IO::FileIOBase::GetDirectInstance()->FindFiles(searchDirectory.c_str(), pattern.c_str(), [&](const char* filePath) -> bool
{
AZ::IO::ArchiveFileIterator fileIterator{ nullptr, AZ::IO::PathView(filePath).Filename().Native(), {} };
ArchiveFile archiveFile{ AZ::IO::PathView(filePath).Filename().Native(), {} };
if (AZ::IO::FileIOBase::GetDirectInstance()->IsDirectory(filePath))
{
fileIterator.m_fileDesc.nAttrib = fileIterator.m_fileDesc.nAttrib | AZ::IO::FileDesc::Attribute::Subdirectory;
m_fileSet.emplace(AZStd::move(fileIterator));
archiveFile.m_fileDesc.nAttrib = archiveFile.m_fileDesc.nAttrib | AZ::IO::FileDesc::Attribute::Subdirectory;
m_fileSet.emplace(AZStd::move(archiveFile));
}
else
{
if (AZ::IO::FileIOBase::GetDirectInstance()->IsReadOnly(filePath))
{
fileIterator.m_fileDesc.nAttrib = fileIterator.m_fileDesc.nAttrib | AZ::IO::FileDesc::Attribute::ReadOnly;
archiveFile.m_fileDesc.nAttrib = archiveFile.m_fileDesc.nAttrib | AZ::IO::FileDesc::Attribute::ReadOnly;
}
AZ::u64 fileSize = 0;
AZ::IO::FileIOBase::GetDirectInstance()->Size(filePath, fileSize);
fileIterator.m_fileDesc.nSize = fileSize;
fileIterator.m_fileDesc.tWrite = AZ::IO::FileIOBase::GetDirectInstance()->ModificationTime(filePath);
archiveFile.m_fileDesc.nSize = fileSize;
archiveFile.m_fileDesc.tWrite = AZ::IO::FileIOBase::GetDirectInstance()->ModificationTime(filePath);
// These times are not supported by our file interface
fileIterator.m_fileDesc.tAccess = fileIterator.m_fileDesc.tWrite;
fileIterator.m_fileDesc.tCreate = fileIterator.m_fileDesc.tWrite;
m_fileSet.emplace(AZStd::move(fileIterator));
archiveFile.m_fileDesc.tAccess = archiveFile.m_fileDesc.tWrite;
archiveFile.m_fileDesc.tCreate = archiveFile.m_fileDesc.tWrite;
m_fileSet.emplace(AZStd::move(archiveFile));
}
return true;
});
};
AZ::IO::FileIOBase::GetDirectInstance()->FindFiles(searchDirectory.c_str(), pattern.c_str(), ScanFileSystem);
}
//////////////////////////////////////////////////////////////////////////
@@ -180,7 +163,7 @@ namespace AZ::IO
fileDesc.nAttrib = AZ::IO::FileDesc::Attribute::ReadOnly | AZ::IO::FileDesc::Attribute::Archive;
fileDesc.nSize = fileEntry->desc.lSizeUncompressed;
fileDesc.tWrite = fileEntry->GetModificationTime();
m_fileSet.emplace(AZ::IO::ArchiveFileIterator{ this, fname, fileDesc });
m_fileSet.emplace(fname, fileDesc);
}
ZipDir::FindDir findDirectoryEntry(zipCache);
@@ -193,7 +176,7 @@ namespace AZ::IO
}
AZ::IO::FileDesc fileDesc;
fileDesc.nAttrib = AZ::IO::FileDesc::Attribute::ReadOnly | AZ::IO::FileDesc::Attribute::Archive | AZ::IO::FileDesc::Attribute::Subdirectory;
m_fileSet.emplace(AZ::IO::ArchiveFileIterator{ this, fname, fileDesc });
m_fileSet.emplace(fname, fileDesc);
}
};
@@ -208,30 +191,16 @@ namespace AZ::IO
// so there's really no way to filter out opening the pack and looking at the files inside.
// however, the bind root is not part of the inner zip entry name either
// and the ZipDir::FindFile actually expects just the chopped off piece.
// we have to find whats in common between them and check that:
// we have to find the common path segments between them and check that:
auto resolvedBindRoot = AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(it->m_pathBindRoot);
if (!resolvedBindRoot)
AZ::IO::FixedMaxPath bindRoot;
if (!AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(bindRoot, it->m_pathBindRoot))
{
AZ_Assert(false, "Unable to resolve Path for archive %s bind root %s", it->GetFullPath(), it->m_pathBindRoot.c_str());
return;
}
AZ::IO::FixedMaxPath bindRoot{ *resolvedBindRoot };
auto [bindRootIter, sourcePathIter] = AZStd::mismatch(AZStd::begin(bindRoot), AZStd::end(bindRoot),
AZStd::begin(sourcePath), AZStd::end(sourcePath));
if (sourcePathIter == AZStd::begin(sourcePath))
{
// The path has no characters in common , early out the search as filepath is not part of the iterated zip
continue;
}
AZ::IO::FixedMaxPath sourcePathRemainder;
for (; sourcePathIter != AZStd::end(sourcePath); ++sourcePathIter)
{
sourcePathRemainder /= *sourcePathIter;
}
// Example:
// "@assets@\\levels\\*" <--- szDir
// "@assets@\\" <--- mount point
@@ -256,18 +225,26 @@ namespace AZ::IO
// then it means that the pack's mount point itself might be a return value, not the files inside the pack
// in that case, we compare the mount point remainder itself with the search filter
auto [bindRootIter, sourcePathIter] = AZStd::mismatch(bindRoot.begin(), bindRoot.end(),
sourcePath.begin(), sourcePath.end());
if (bindRootIter != bindRoot.end())
{
AZ::IO::FixedMaxPath sourcePathRemainder;
for (; sourcePathIter != sourcePath.end(); ++sourcePathIter)
{
sourcePathRemainder /= *sourcePathIter;
}
// Retrieve next path component of the mount point remainder
if (!bindRootIter->empty() && AZStd::wildcard_match(sourcePathRemainder.Native(), bindRootIter->Native()))
if (!bindRootIter->empty() && bindRootIter->Match(sourcePathRemainder.Native()))
{
AZ::IO::FileDesc fileDesc{ AZ::IO::FileDesc::Attribute::ReadOnly | AZ::IO::FileDesc::Attribute::Archive | AZ::IO::FileDesc::Attribute::Subdirectory };
m_fileSet.emplace(AZ::IO::ArchiveFileIterator{ this, bindRootIter->Native(), fileDesc });
m_fileSet.emplace(AZStd::move(bindRootIter->Native()), fileDesc);
}
}
else
{
AZ::IO::FixedMaxPath sourcePathRemainder = sourcePath.LexicallyRelative(bindRoot);
// if we get here, it means that the search pattern's root and the mount point for this pack are identical
// which means we may search inside the pack.
ScanInZip(it->pZip.get(), sourcePathRemainder.Native());
@@ -280,17 +257,17 @@ namespace AZ::IO
{
if (m_fileSet.empty())
{
AZ::IO::ArchiveFileIterator emptyFileIterator;
emptyFileIterator.m_lastFetchValid = false;
emptyFileIterator.m_findData = this;
return emptyFileIterator;
return {};
}
// Remove Fetched item from the FindData map so that the iteration continues
AZ::IO::ArchiveFileIterator fileIterator{ *m_fileSet.begin() };
AZ::IO::ArchiveFileIterator fileIterator;
auto archiveFileIt = m_fileSet.begin();
fileIterator.m_filename = archiveFileIt->m_filename;
fileIterator.m_fileDesc = archiveFileIt->m_fileDesc;
fileIterator.m_lastFetchValid = true;
fileIterator.m_findData = this;
m_fileSet.erase(m_fileSet.begin());
m_fileSet.erase(archiveFileIt);
return fileIterator;
}
}
@@ -36,56 +36,72 @@ namespace AZ::IO
AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::IO::FileDesc::Attribute);
inline constexpr size_t ArchiveFilenameMaxLength = 256;
using ArchiveFileString = AZStd::fixed_string<ArchiveFilenameMaxLength>;
class FindData;
//! This is not really an iterator, but a handle
//! that extends ownership of any found filenames from an archive file or the file system
struct ArchiveFileIterator
{
ArchiveFileIterator() = default;
ArchiveFileIterator(FindData* findData, AZStd::string_view filename, const FileDesc& fileDesc);
explicit ArchiveFileIterator(FindData* findData);
ArchiveFileIterator operator++();
ArchiveFileIterator operator++(int);
bool operator==(const AZ::IO::ArchiveFileIterator& rhs) const;
explicit operator bool() const;
size_t GetHash() const;
inline static constexpr size_t FilenameMaxLength = 256;
AZStd::fixed_string<FilenameMaxLength> m_filename;
ArchiveFileString m_filename;
FileDesc m_fileDesc;
AZStd::intrusive_ptr<FindData> m_findData{};
private:
friend class FindData;
friend class Archive;
AZStd::intrusive_ptr<FindData> m_findData;
bool m_lastFetchValid{};
};
struct ArchiveFileIteratorHash
{
size_t operator()(const AZ::IO::ArchiveFileIterator& iter) const;
};
struct AZStdStringLessCaseInsensitive
{
bool operator()(AZStd::string_view left, AZStd::string_view right) const;
using is_transparent = void;
};
class FindData
: public AZStd::intrusive_base
{
public:
AZ_CLASS_ALLOCATOR(FindData, AZ::SystemAllocator, 0);
FindData() = default;
AZ::IO::ArchiveFileIterator Fetch();
ArchiveFileIterator Fetch();
void Scan(IArchive* archive, AZStd::string_view path, bool bAllowUseFS = false, bool bScanZips = true);
protected:
void ScanFS(IArchive* archive, AZStd::string_view path);
// Populates the FileSet with files within the that match the path pattern that is
// if it refers to a file within a bound archive root or returns the archive root
// path if the path pattern matches it.
void ScanZips(IArchive* archive, AZStd::string_view path);
using FileSet = AZStd::unordered_set<ArchiveFileIterator, ArchiveFileIteratorHash>;
class ArchiveFile
{
public:
friend class FindData;
ArchiveFile();
ArchiveFile(AZStd::string_view filename, const FileDesc& fileDesc);
size_t GetHash() const;
bool operator==(const ArchiveFile& rhs) const;
private:
ArchiveFileString m_filename;
FileDesc m_fileDesc;
};
struct ArchiveFileHash
{
size_t operator()(const ArchiveFile& archiveFile) const;
};
using FileSet = AZStd::unordered_set<ArchiveFile, ArchiveFileHash>;
FileSet m_fileSet;
};
}
@@ -12,12 +12,10 @@
#include <AzCore/EBus/Event.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/smart_ptr/intrusive_base.h>
#include <AzCore/std/smart_ptr/intrusive_ptr.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/Archive/ArchiveFindData.h>
@@ -106,66 +104,6 @@ namespace AZ::IO
{
AZ_RTTI(IArchive, "{764A2260-FF8A-4C86-B958-EBB0B69D9DFA}");
using FileTime = uint64_t;
// Flags used in file path resolution rules
enum EPathResolutionRules
{
// If used, the source path will be treated as the destination path
// and no transformations will be done. Pass this flag when the path is to be the actual
// path on the disk/in the packs and doesn't need adjustment (or after it has come through adjustments already)
// if this is set, AdjustFileName will not map the input path into the folder (Ex: Shaders will not be converted to Game\Shaders)
FLAGS_PATH_REAL = 1 << 16,
// AdjustFileName will always copy the file path to the destination path:
// regardless of the returned value, szDestpath can be used
FLAGS_COPY_DEST_ALWAYS = 1 << 17,
// Adds trailing slash to the path
FLAGS_ADD_TRAILING_SLASH = 1L << 18,
// if this is set, AdjustFileName will not make relative paths into full paths
FLAGS_NO_FULL_PATH = 1 << 21,
// if this is set, AdjustFileName will redirect path to disc
FLAGS_REDIRECT_TO_DISC = 1 << 22,
// if this is set, AdjustFileName will not adjust path for writing files
FLAGS_FOR_WRITING = 1 << 23,
// if this is set, the archive would be stored in memory (gpu)
FLAGS_PAK_IN_MEMORY = 1 << 25,
// Store all file names as crc32 in a flat directory structure.
FLAGS_FILENAMES_AS_CRC32 = 1 << 26,
// if this is set, AdjustFileName will try to find the file under any mod paths we know about
FLAGS_CHECK_MOD_PATHS = 1 << 27,
// if this is set, AdjustFileName will always check the filesystem/disk and not check inside open archives
FLAGS_NEVER_IN_PAK = 1 << 28,
// returns existing file name from the local data or existing cache file name
// used by the resource compiler to pass the real file name
FLAGS_RESOLVE_TO_CACHE = 1 << 29,
// if this is set, the archive would be stored in memory (cpu)
FLAGS_PAK_IN_MEMORY_CPU = 1 << 30,
// if this is set, the level pak is inside another archive
FLAGS_LEVEL_PAK_INSIDE_PAK = 1 << 31,
};
// Used for widening FOpen functionality. They're ignored for the regular File System files.
enum EFOpenFlags
{
// If possible, will prevent the file from being read from memory.
FOPEN_HINT_DIRECT_OPERATION = 1,
// Will prevent a "missing file" warnings to be created.
FOPEN_HINT_QUIET = 1 << 1,
// File should be on disk
FOPEN_ONDISK = 1 << 2,
// Open is done by the streaming thread.
FOPEN_FORSTREAMING = 1 << 3,
};
//
enum ERecordFileOpenList
@@ -175,8 +113,6 @@ namespace AZ::IO
RFOM_Level, // during level loading till export2game -> resourcelist.txt, used to generate the list for level2level loading
RFOM_NextLevel // used for level2level loading
};
// the size of the buffer that receives the full path to the file
inline static constexpr size_t MaxPath = 1024;
//file location enum used in isFileExist to control where the archive system looks for the file.
enum EFileSearchLocation
@@ -205,63 +141,31 @@ namespace AZ::IO
virtual ~IArchive() = default;
/**
* Deprecated: Use the AZ::IO::FileIOBase::ResolvePath function below that doesn't accept the nFlags or skipMods parameters
* given the source relative path, constructs the full path to the file according to the flags
* returns the pointer to the constructed path (can be either szSourcePath, or szDestPath, or NULL in case of error
*/
//
virtual const char* AdjustFileName(AZStd::string_view src, char* dst, size_t dstSize, uint32_t nFlags, bool skipMods = false) = 0;
virtual bool Init(AZStd::string_view szBasePath) = 0;
virtual void Release() = 0;
// Summary:
// Returns true if given archivepath is installed to HDD
// If no file path is given it will return true if whole application is installed to HDD
virtual bool IsInstalledToHDD(AZStd::string_view acFilePath = 0) const = 0;
// after this call, the archive file will be searched for files when they aren't on the OS file system
// Arguments:
// pName - must not be 0
virtual bool OpenPack(AZStd::string_view pName, uint32_t nFlags = FLAGS_PATH_REAL, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = {},
virtual bool OpenPack(AZStd::string_view pName, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = {},
AZ::IO::FixedMaxPathString* pFullPath = nullptr, bool addLevels = true) = 0;
// after this call, the archive file will be searched for files when they aren't on the OS file system
virtual bool OpenPack(AZStd::string_view pBindingRoot, AZStd::string_view pName, uint32_t nFlags = FLAGS_PATH_REAL,
virtual bool OpenPack(AZStd::string_view pBindingRoot, AZStd::string_view pName,
AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = {}, AZ::IO::FixedMaxPathString* pFullPath = nullptr, bool addLevels = true) = 0;
// after this call, the file will be unlocked and closed, and its contents won't be used to search for files
virtual bool ClosePack(AZStd::string_view pName, uint32_t nFlags = FLAGS_PATH_REAL) = 0;
virtual bool ClosePack(AZStd::string_view pName) = 0;
// opens pack files by the path and wildcard
virtual bool OpenPacks(AZStd::string_view pWildcard, uint32_t nFlags = FLAGS_PATH_REAL, AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr) = 0;
virtual bool OpenPacks(AZStd::string_view pWildcard, AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr) = 0;
// opens pack files by the path and wildcard
virtual bool OpenPacks(AZStd::string_view pBindingRoot, AZStd::string_view pWildcard, uint32_t nFlags = FLAGS_PATH_REAL,
virtual bool OpenPacks(AZStd::string_view pBindingRoot, AZStd::string_view pWildcard,
AZStd::vector<AZ::IO::FixedMaxPathString>* pFullPaths = nullptr) = 0;
// closes pack files by the path and wildcard
virtual bool ClosePacks(AZStd::string_view pWildcard, uint32_t nFlags = FLAGS_PATH_REAL) = 0;
virtual bool ClosePacks(AZStd::string_view pWildcard) = 0;
//returns if a archive exists matching the wildcard
virtual bool FindPacks(AZStd::string_view pWildcardIn) = 0;
// Set access status of a archive files with a wildcard
virtual bool SetPacksAccessible(bool bAccessible, AZStd::string_view pWildcard, uint32_t nFlags = FLAGS_PATH_REAL) = 0;
virtual bool SetPacksAccessible(bool bAccessible, AZStd::string_view pWildcard) = 0;
// Set access status of a pack file
virtual bool SetPackAccessible(bool bAccessible, AZStd::string_view pName, uint32_t nFlags = FLAGS_PATH_REAL) = 0;
// Load or unload archive file completely to memory.
virtual bool LoadPakToMemory(AZStd::string_view pName, EInMemoryArchiveLocation eLoadToMemory, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pMemoryBlock = nullptr) = 0;
virtual void LoadPaksToMemory(int nMaxArchiveSize, bool bLoadToMemory) = 0;
// Processes an alias command line containing multiple aliases.
virtual void ParseAliases(AZStd::string_view szCommandLine) = 0;
// adds or removes an alias from the list
virtual void SetAlias(AZStd::string_view szName, AZStd::string_view szAlias, bool bAdd) = 0;
// gets an alias from the list, if any exist.
// if bReturnSame==true, it will return the input name if an alias doesn't exist. Otherwise returns NULL
virtual const char* GetAlias(AZStd::string_view szName, bool bReturnSame = true) = 0;
// lock all the operations
virtual void Lock() = 0;
virtual void Unlock() = 0;
virtual bool SetPackAccessible(bool bAccessible, AZStd::string_view pName) = 0;
// Set and Get the localization folder name (Languages, Localization, ...)
virtual void SetLocalizationFolder(AZStd::string_view sLocalizationFolder) = 0;
@@ -273,28 +177,19 @@ namespace AZ::IO
// ex: AZ::IO::HandleType fileHandle = FOpen( "test.txt","rbx" );
// mode x is a direct access mode, when used file reads will go directly into the low level file system without any internal data caching.
// Text mode is not supported for files in Archives.
// for nFlags @see IArchive::EFOpenFlags
virtual AZ::IO::HandleType FOpen(AZStd::string_view pName, const char* mode, uint32_t nFlags = 0) = 0;
// Read raw data from file, no endian conversion.
virtual size_t FReadRaw(void* data, size_t length, size_t elems, AZ::IO::HandleType fileHandle) = 0;
// Read all file contents into the provided memory, nSizeOfFile must be the same as returned by GetFileSize(handle)
// Current seek pointer is ignored and reseted to 0.
// no endian conversion.
virtual size_t FReadRawAll(void* data, size_t nFileSize, AZ::IO::HandleType fileHandle) = 0;
virtual AZ::IO::HandleType FOpen(AZStd::string_view pName, const char* mode) = 0;
// Get pointer to the internally cached, loaded data of the file.
// WARNING! The returned pointer is only valid while the fileHandle has not been closed.
virtual void* FGetCachedFileData(AZ::IO::HandleType fileHandle, size_t& nFileSize) = 0;
// Write file data, cannot be used for writing into the Archive.
// Use INestedArchive interface for writing into the archivefiles.
virtual size_t FWrite(const void* data, size_t length, size_t elems, AZ::IO::HandleType fileHandle) = 0;
// Read raw data from file, no endian conversion.
virtual size_t FRead(void* data, size_t bytesToRead, AZ::IO::HandleType fileHandle) = 0;
// Write file data, cannot be used for writing into the Archive.
// Use INestedArchive interface for writing into the archive files.
virtual size_t FWrite(const void* data, size_t bytesToWrite, AZ::IO::HandleType fileHandle) = 0;
virtual int FPrintf(AZ::IO::HandleType fileHandle, const char* format, ...) = 0;
virtual char* FGets(char*, int, AZ::IO::HandleType) = 0;
virtual int Getc(AZ::IO::HandleType) = 0;
virtual size_t FGetSize(AZ::IO::HandleType fileHandle) = 0;
virtual size_t FGetSize(AZStd::string_view pName, bool bAllowUseFileSystem = false) = 0;
virtual bool IsInPak(AZ::IO::HandleType fileHandle) = 0;
@@ -318,7 +213,6 @@ namespace AZ::IO
virtual AZStd::intrusive_ptr<AZ::IO::MemoryBlock> PoolAllocMemoryBlock(size_t nSize, const char* sUsage, size_t nAlign = 1) = 0;
// Arguments:
// nFlags is a combination of EPathResolutionRules flags.
virtual ArchiveFileIterator FindFirst(AZStd::string_view pDir, EFileSearchType searchType = eFileSearchType_AllowInZipsOnly) = 0;
virtual ArchiveFileIterator FindNext(AZ::IO::ArchiveFileIterator handle) = 0;
virtual bool FindClose(AZ::IO::ArchiveFileIterator handle) = 0;
@@ -334,9 +228,6 @@ namespace AZ::IO
virtual IArchive::SignedFileSize GetFileSizeOnDisk(AZStd::string_view filename) = 0;
// creates a directory
virtual bool MakeDir(AZStd::string_view szPath) = 0;
// open the physical archive file - creates if it doesn't exist
// returns NULL if it's invalid or can't open the file
// nFlags is a combination of flags from EArchiveFlags enum.
@@ -344,8 +235,8 @@ namespace AZ::IO
AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr) = 0;
// returns the path to the archive in which the file was opened
// returns NULL if the file is a physical file, and "" if the path to archive is unknown (shouldn't ever happen)
virtual const char* GetFileArchivePath(AZ::IO::HandleType fileHandle) = 0;
// returns empty path view if the file is a physical file
virtual AZ::IO::PathView GetFileArchivePath(AZ::IO::HandleType fileHandle) = 0;
// compresses the raw data into raw data. The buffer for compressed data itself with the heap passed. Uses method 8 (deflate)
// returns one of the Z_* errors (Z_OK upon success)
@@ -378,25 +269,7 @@ namespace AZ::IO
// get the current mode, can be set by RecordFileOpen()
virtual IArchive::ERecordFileOpenList GetRecordFileOpenList() = 0;
// computes CRC (zip compatible) for a file
// useful if a huge uncompressed file is generation in non continuous way
// good for big files - low memory overhead (1MB)
// Arguments:
// szPath - must not be 0
// Returns:
// error code
virtual uint32_t ComputeCRC(AZStd::string_view szPath, uint32_t nFileOpenFlags = 0) = 0;
// computes MD5 checksum for a file
// good for big files - low memory overhead (1MB)
// Arguments:
// szPath - must not be 0
// md5 - destination array of uint8_t [16]
// Returns:
// true if success, false on failure
virtual bool ComputeMD5(AZStd::string_view szPath, uint8_t* md5, uint32_t nFileOpenFlags = 0, bool useDirectFileAccess = false) = 0;
// useful for gathering file access statistics, assert if it was inserted already but then it does not become insersted
// useful for gathering file access statistics, assert if it was inserted already but then it does not become inserted
// Arguments:
// pSink - must not be 0
virtual void RegisterFileAccessSink(IArchiveFileAccessSink* pSink) = 0;
@@ -408,8 +281,6 @@ namespace AZ::IO
// When enabled, files accessed at runtime will be tracked
virtual void DisableRuntimeFileAccess(bool status) = 0;
virtual bool DisableRuntimeFileAccess(bool status, AZStd::thread_id threadId) = 0;
virtual bool CheckFileAccessDisabled(AZStd::string_view name, const char* mode) = 0;
virtual void SetRenderThreadId(AZStd::thread_id renderThreadId) = 0;
// gets the current pak priority
virtual ArchiveLocationPriority GetPakPriority() const = 0;
@@ -431,21 +302,6 @@ namespace AZ::IO
using LevelPackCloseEvent = AZ::Event<AZStd::string_view>;
virtual auto GetLevelPackCloseEvent()->LevelPackCloseEvent* = 0;
// Type-safe endian conversion read.
template<class T>
size_t FRead(T* data, size_t elems, AZ::IO::HandleType fileHandle, bool bSwapEndian = false)
{
size_t count = FReadRaw(data, sizeof(T), elems, fileHandle);
SwapEndian(data, count, bSwapEndian);
return count;
}
// Type-independent Write.
template<class T>
void FWrite(T* data, size_t elems, AZ::IO::HandleType fileHandle)
{
FWrite((void*)data, sizeof(T), elems, fileHandle);
}
inline static constexpr IArchive::SignedFileSize FILE_NOT_PRESENT = -1;
};
@@ -9,9 +9,9 @@
#pragma once
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/std/smart_ptr/intrusive_base.h>
#include <AzCore/std/string/string_view.h>
#include <AzFramework/Archive/Codec.h>
namespace AZ::IO
@@ -71,28 +71,10 @@ namespace AZ::IO
// multiple times
FLAGS_DONT_COMPACT = 1 << 5,
// flag is set when complete pak has been loaded into memory
FLAGS_IN_MEMORY = 1 << 6,
FLAGS_IN_MEMORY_CPU = 1 << 7,
FLAGS_IN_MEMORY_MASK = FLAGS_IN_MEMORY | FLAGS_IN_MEMORY_CPU,
// Store all file names as crc32 in a flat directory structure.
FLAGS_FILENAMES_AS_CRC32 = 1 << 8,
// flag is set when pak is stored on HDD
FLAGS_ON_HDD = 1 << 9,
//Override pak - paks opened with this flag go at the end of the list and contents will be found before other paks
//Used for patching
FLAGS_OVERRIDE_PAK = 1 << 10,
// Disable a pak file without unloading it, this flag is used in combination with patches and multiplayer
// to ensure that specific paks stay in the position(to keep the same priority) but beeing disabled
// to ensure that specific paks stay in the position(to keep the same priority) but being disabled
// when running multiplayer
FLAGS_DISABLE_PAK = 1 << 11,
// flag is set when pak is inside another pak
FLAGS_INSIDE_PAK = 1 << 12,
};
using Handle = void*;
@@ -122,7 +104,7 @@ namespace AZ::IO
virtual int StartContinuousFileUpdate(AZStd::string_view szRelativePath, uint64_t nSize) = 0;
// Summary:
// Adds a new file to the zip or update an existing's segment if it is not compressed - just stored
// Adds a new file to the zip or update an existing segment if it is not compressed - just stored
// adds a directory (creates several nested directories if needed)
// ( name might be misleading as if nOverwriteSeekPos is used the update is not continuous )
// Arguments:
@@ -164,7 +146,7 @@ namespace AZ::IO
// Summary:
// Get the full path to the archive file.
virtual const char* GetFullPath() const = 0;
virtual AZ::IO::PathView GetFullPath() const = 0;
// Summary:
// Get the flags of this object.
@@ -174,7 +174,7 @@ namespace AZ::IO
return m_pCache->ReadFile(reinterpret_cast<ZipDir::FileEntry*>(fileHandle), nullptr, pBuffer);
}
const char* NestedArchive::GetFullPath() const
AZ::IO::PathView NestedArchive::GetFullPath() const
{
return m_pCache->GetFilePath();
}
@@ -193,19 +193,9 @@ namespace AZ::IO
if (nFlagsToSet & FLAGS_RELATIVE_PATHS_ONLY)
{
m_nFlags |= FLAGS_RELATIVE_PATHS_ONLY;
}
if (nFlagsToSet & FLAGS_ON_HDD)
{
m_nFlags |= FLAGS_ON_HDD;
}
if (nFlagsToSet & FLAGS_RELATIVE_PATHS_ONLY ||
nFlagsToSet & FLAGS_ON_HDD)
{
// we don't support changing of any other flags
return true;
}
return false;
}
@@ -252,20 +242,12 @@ namespace AZ::IO
return AZ::IO::FixedMaxPathString{ szRelativePath };
}
if ((szRelativePath.size() > 1 && szRelativePath[1] == ':') || (m_nFlags & FLAGS_ABSOLUTE_PATHS))
if ((m_nFlags & FLAGS_ABSOLUTE_PATHS) == FLAGS_ABSOLUTE_PATHS)
{
// make the normalized full path and try to match it against the binding root of this object
auto resolvedPath = AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(szRelativePath);
// Make sure the resolve path is longer than the bind root and that it starts with the bind root
if (!resolvedPath || resolvedPath->Native().size() <= m_strBindRoot.size() || azstrnicmp(resolvedPath->c_str(), m_strBindRoot.c_str(), m_strBindRoot.size()) != 0)
{
return {};
}
// Remove the bind root prefix from the resolved path
resolvedPath->Native().erase(0, m_strBindRoot.size() + 1);
return resolvedPath->Native();
AZ::IO::FixedMaxPath resolvedPath;
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(resolvedPath, szRelativePath);
return resolvedPath.LexicallyProximate(m_strBindRoot).Native();
}
return AZ::IO::FixedMaxPathString{ szRelativePath };
@@ -19,15 +19,15 @@ namespace AZ::IO
{
bool operator()(const INestedArchive* left, const INestedArchive* right) const
{
return azstricmp(left->GetFullPath(), right->GetFullPath()) < 0;
return left->GetFullPath() < right->GetFullPath();
}
bool operator()(AZStd::string_view left, const INestedArchive* right) const
{
return azstrnicmp(left.data(), right->GetFullPath(), left.size()) < 0;
return AZ::IO::PathView(left) < right->GetFullPath();
}
bool operator()(const INestedArchive* left, AZStd::string_view right) const
{
return azstrnicmp(left->GetFullPath(), right.data(), right.size()) < 0;
return left->GetFullPath() < AZ::IO::PathView(right);
}
};
@@ -78,7 +78,7 @@ namespace AZ::IO
int ReadFile(Handle fileHandle, void* pBuffer) override;
// returns the full path to the archive file
const char* GetFullPath() const override;
AZ::IO::PathView GetFullPath() const override;
ZipDir::Cache* GetCache();
uint32_t GetFlags() const override;
@@ -95,7 +95,7 @@ namespace AZ::IO
ZipDir::CachePtr m_pCache;
// the binding root may be empty string - in this case, the absolute path binding won't work
AZStd::string m_strBindRoot;
AZ::IO::Path m_strBindRoot;
IArchive* m_archive{};
uint32_t m_nFlags{};
};
@@ -9,7 +9,6 @@
#include <AzCore/Console/Console.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/string/conversions.h>
#include <AzFramework/Archive/ZipFileFormat.h>
@@ -104,24 +103,21 @@ namespace AZ::IO::ZipDir
: m_pCache(pCache)
, m_bCommitted(false)
{
AZ::IO::PathString normalizedPath{ szRelativePath };
AZ::StringFunc::Path::Normalize(normalizedPath);
AZStd::to_lower(AZStd::begin(normalizedPath), AZStd::end(normalizedPath));
// Update the cache string pool with the relative path to the file
auto pathIt = m_pCache->m_relativePathPool.emplace(normalizedPath);
auto pathIt = m_pCache->m_relativePathPool.emplace(AZ::IO::PathView(szRelativePath).LexicallyNormal());
m_szRelativePath = *pathIt.first;
// this is the name of the directory - create it or find it
m_pFileEntry = m_pCache->GetRoot()->Add(m_szRelativePath);
m_pFileEntry = m_pCache->GetRoot()->Add(m_szRelativePath.Native());
if (m_pFileEntry && az_archive_zip_directory_cache_verbosity)
{
AZ_TracePrintf("Archive", R"(File "%s" has been added to archive at root "%s")", normalizedPath.c_str(), pCache->GetFilePath());
AZ_TracePrintf("Archive", R"(File "%s" has been added to archive at root "%s")", pathIt.first->c_str(), pCache->GetFilePath());
}
}
~FileEntryTransactionAdd()
{
if (m_pFileEntry && !m_bCommitted)
{
m_pCache->RemoveFile(m_szRelativePath);
m_pCache->RemoveFile(m_szRelativePath.Native());
m_pCache->m_relativePathPool.erase(m_szRelativePath);
}
}
@@ -131,11 +127,11 @@ namespace AZ::IO::ZipDir
}
AZStd::string_view GetRelativePath() const
{
return m_szRelativePath;
return m_szRelativePath.Native();
}
private:
Cache* m_pCache;
AZStd::string_view m_szRelativePath;
AZ::IO::PathView m_szRelativePath;
FileEntry* m_pFileEntry;
bool m_bCommitted;
};
@@ -587,34 +583,27 @@ namespace AZ::IO::ZipDir
// deletes the file from the archive
ErrorEnum Cache::RemoveFile(AZStd::string_view szRelativePathSrc)
{
// Normalize and lower case the relative path
AZ::IO::PathString szRelativePath{ szRelativePathSrc };
AZ::StringFunc::Path::Normalize(szRelativePath);
AZStd::to_lower(AZStd::begin(szRelativePath), AZStd::end(szRelativePath));
AZStd::string_view normalizedRelativePath = szRelativePath;
// find the last slash in the path
size_t slashOffset = normalizedRelativePath.find_last_of(AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR);
AZ::IO::PathView szRelativePath{ szRelativePathSrc };
AZStd::string_view fileName; // the name of the file to delete
FileEntryTree* pDir; // the dir from which the subdir will be deleted
if (slashOffset != AZStd::string_view::npos)
if (szRelativePath.HasParentPath())
{
FindDir fd(GetRoot());
// the directory to remove
pDir = fd.FindExact(normalizedRelativePath.substr(0, slashOffset));
pDir = fd.FindExact(szRelativePath.ParentPath());
if (!pDir)
{
return ZD_ERROR_DIR_NOT_FOUND;// there is no such directory
}
fileName = normalizedRelativePath.substr(slashOffset + 1);
fileName = szRelativePath.Filename().Native();
}
else
{
pDir = GetRoot();
fileName = normalizedRelativePath;
fileName = szRelativePath.Native();
}
ErrorEnum e = pDir->RemoveFile(fileName);
@@ -625,7 +614,7 @@ namespace AZ::IO::ZipDir
if (az_archive_zip_directory_cache_verbosity)
{
AZ_TracePrintf("Archive", R"(File "%.*s" has been remove from archive at root "%s")",
aznumeric_cast<int>(fileName.size()), fileName.data(), GetFilePath());
AZ_STRING_ARG(szRelativePath.Native()), GetFilePath());
}
}
return e;
@@ -635,45 +624,38 @@ namespace AZ::IO::ZipDir
// deletes the directory, with all its descendants (files and subdirs)
ErrorEnum Cache::RemoveDir(AZStd::string_view szRelativePathSrc)
{
// Normalize and lower case the relative path
AZ::IO::PathString szRelativePath{ szRelativePathSrc };
AZ::StringFunc::Path::Normalize(szRelativePath);
AZStd::to_lower(AZStd::begin(szRelativePath), AZStd::end(szRelativePath));
AZStd::string_view normalizedRelativePath = szRelativePath;
// find the last slash in the path
size_t slashOffset = normalizedRelativePath.find_last_of(AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR);
AZ::IO::PathView szRelativePath{ szRelativePathSrc };
AZStd::string_view dirName; // the name of the dir to delete
FileEntryTree* pDir; // the dir from which the subdir will be deleted
if (slashOffset != AZStd::string_view::npos)
if (szRelativePath.HasParentPath())
{
FindDir fd(GetRoot());
// the directory to remove
pDir = fd.FindExact(normalizedRelativePath.substr(0, slashOffset));
pDir = fd.FindExact(szRelativePath.ParentPath());
if (!pDir)
{
return ZD_ERROR_DIR_NOT_FOUND;// there is no such directory
}
dirName = normalizedRelativePath.substr(slashOffset + 1);
dirName = szRelativePath.Filename().Native();
}
else
{
pDir = GetRoot();
dirName = normalizedRelativePath;
dirName = szRelativePath.Native();
}
ErrorEnum e = pDir->RemoveDir(normalizedRelativePath);
ErrorEnum e = pDir->RemoveDir(dirName);
if (e == ZD_ERROR_SUCCESS)
{
m_nFlags |= FLAGS_UNCOMPACTED | FLAGS_CDR_DIRTY;
if (az_archive_zip_directory_cache_verbosity)
{
AZ_TracePrintf("Archive", R"(File "%.*s" has been remove from archive at root "%s")",
aznumeric_cast<int>(normalizedRelativePath.size()), normalizedRelativePath.data(), GetFilePath());
AZ_TracePrintf("Archive", R"(Directory "%.*s" has been remove from archive at root "%s")",
AZ_STRING_ARG(szRelativePath.Native()), GetFilePath());
}
}
return e;
@@ -769,9 +751,7 @@ namespace AZ::IO::ZipDir
// finds the file by exact path
FileEntry* Cache::FindFile(AZStd::string_view szPathSrc, [[maybe_unused]] bool bFullInfo)
{
AZ::IO::PathString szPath{ szPathSrc };
AZ::StringFunc::Path::Normalize(szPath);
AZStd::to_lower(AZStd::begin(szPath), AZStd::end(szPath));
AZ::IO::PathView szPath{ szPathSrc };
ZipDir::FindFile fd(GetRoot());
FileEntry* fileEntry = fd.FindExact(szPath);
@@ -779,19 +759,13 @@ namespace AZ::IO::ZipDir
{
if (az_archive_zip_directory_cache_verbosity)
{
AZ_TracePrintf("Archive", "FindExact failed to find file %s at root %s", szPath.c_str(), GetFilePath());
AZ_TracePrintf("Archive", "FindExact failed to find file %.*s at root %s", AZ_STRING_ARG(szPath.Native()), GetFilePath());
}
return {};
}
return fileEntry;
}
// returns the size of memory occupied by the instance referred to by this cache
size_t Cache::GetSize() const
{
return sizeof(*this) + m_strFilePath.capacity() + m_treeDir.GetSize() - sizeof(m_treeDir);
}
// refreshes information about the given file entry into this file entry
ErrorEnum Cache::Refresh(FileEntryBase* pFileEntry)
{
@@ -16,6 +16,7 @@
#pragma once
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/std/smart_ptr/intrusive_base.h>
#include <AzFramework/Archive/Codec.h>
@@ -89,9 +90,6 @@ namespace AZ::IO::ZipDir
// refreshes information about the given file entry into this file entry
ErrorEnum Refresh(FileEntryBase* pFileEntry);
// returns the size of memory occupied by the instance of this cache
size_t GetSize() const;
// QUICK check to determine whether the file entry belongs to this object
bool IsOwnerOf(const FileEntry* pFileEntry) const
{
@@ -100,9 +98,9 @@ namespace AZ::IO::ZipDir
// returns the string - path to the zip file from which this object was constructed.
// this will be "" if the object was constructed with a factory that wasn't created with FLAGS_MEMORIZE_ZIP_PATH
const char* GetFilePath() const
AZ::IO::PathView GetFilePath() const
{
return m_strFilePath.c_str();
return m_strFilePath;
}
FileEntryTree* GetRoot()
@@ -135,10 +133,10 @@ namespace AZ::IO::ZipDir
FileEntryTree m_treeDir;
AZ::IO::HandleType m_fileHandle;
AZ::IAllocatorAllocate* m_allocator;
AZStd::string m_strFilePath;
AZ::IO::Path m_strFilePath;
// String Pool for persistently storing paths as long as they reside in the cache
AZStd::unordered_set<AZStd::string> m_relativePathPool;
AZStd::unordered_set<AZ::IO::Path> m_relativePathPool;
// offset to the start of CDR in the file,even if there's no CDR there currently
// when a new file is added, it can start from here, but this value will need to be updated then
@@ -41,13 +41,6 @@ namespace AZ::IO::ZipDir
m_encryptedHeaders = ZipFile::HEADERS_NOT_ENCRYPTED;
m_signedHeaders = ZipFile::HEADERS_NOT_SIGNED;
if (m_nFlags & FLAGS_FILENAMES_AS_CRC32)
{
m_bBuildFileEntryMap = false;
m_bBuildFileEntryTree = false;
m_bBuildOptimizedFileEntry = true;
}
if (m_nFlags & FLAGS_READ_INSIDE_PAK)
{
m_fileExt.m_fileIOBase = AZ::IO::FileIOBase::GetInstance();
@@ -88,12 +81,12 @@ namespace AZ::IO::ZipDir
if (m_fileExt.m_fileHandle == AZ::IO::InvalidHandle)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Could not open file in binary mode for reading");
AZ_Warning("Archive", false, R"(ZD_ERROR_IO_FAILED: Could not open file "%s" in binary mode for reading)", szFileName);
return {};
}
if (!ReadCache(*pCache))
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Could not read the CDR of the pack file.");
AZ_Warning("Archive", false, R"(ZD_ERROR_IO_FAILED: Could not read the CDR of the pack file "%s".)", pCache->m_strFilePath.c_str());
return {};
}
}
@@ -113,12 +106,12 @@ namespace AZ::IO::ZipDir
size_t nFileSize = (size_t)Tell();
Seek(0, SEEK_SET);
AZ_Assert(nFileSize != 0, "File of size 0 will not be open for reading");
AZ_Warning("Archive", nFileSize != 0, R"(ZD_ERROR_IO_FAILED: File "%s" with size 0 will not be open for reading)", szFileName);
if (nFileSize)
{
if (!ReadCache(*pCache))
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Could not open file in binary mode for reading");
AZ_Warning("Archive", false, R"(ZD_ERROR_IO_FAILED: Could not open file "%s" in binary mode for reading)", szFileName);
return {};
}
bOpenForWriting = false;
@@ -143,7 +136,7 @@ namespace AZ::IO::ZipDir
if (m_fileExt.m_fileHandle == AZ::IO::InvalidHandle)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Could not open file in binary mode for appending (read/write)");
AZ_Warning("Archive", false, R"(ZD_ERROR_IO_FAILED: Could not open file "%s" in binary mode for appending (read/write))", szFileName);
return {};
}
}
@@ -211,7 +204,7 @@ namespace AZ::IO::ZipDir
if (m_headerExtended.nHeaderSize != sizeof(m_headerExtended))
{
// Extended Header is not valid
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Bad extended header");
AZ_Warning("Archive", false, "ZD_ERROR_DATA_IS_CORRUPT: Bad extended header");
return false;
}
//We have the header, so read the encryption and signing techniques
@@ -224,7 +217,7 @@ namespace AZ::IO::ZipDir
if (m_headerExtended.nEncryption != ZipFile::HEADERS_NOT_ENCRYPTED && m_encryptedHeaders != ZipFile::HEADERS_NOT_ENCRYPTED)
{
//Encryption technique has been specified in both the disk number (old technique) and the custom header (new technique).
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Unexpected encryption technique in header");
AZ_Warning("Archive", false, "ZD_ERROR_DATA_IS_CORRUPT: Unexpected encryption technique in header");
return false;
}
else
@@ -240,7 +233,7 @@ namespace AZ::IO::ZipDir
break;
default:
// Unexpected technique
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Bad encryption technique in header");
AZ_Warning("Archive", false, "ZD_ERROR_DATA_IS_CORRUPT: Bad encryption technique in header");
return false;
}
}
@@ -255,7 +248,7 @@ namespace AZ::IO::ZipDir
break;
default:
// Unexpected technique
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Bad signing technique in header");
AZ_Warning("Archive", false, "ZD_ERROR_DATA_IS_CORRUPT: Bad signing technique in header");
return false;
}
@@ -266,7 +259,7 @@ namespace AZ::IO::ZipDir
Read(&m_headerSignature, sizeof(m_headerSignature));
if (m_headerSignature.nHeaderSize != sizeof(m_headerSignature))
{
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Bad signature header");
AZ_Warning("Archive", false, "ZD_ERROR_DATA_IS_CORRUPT: Bad signature header");
return false;
}
}
@@ -274,7 +267,7 @@ namespace AZ::IO::ZipDir
else
{
// Unexpected technique
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Comment field is the wrong length");
AZ_Warning("Archive", false, "ZD_ERROR_DATA_IS_CORRUPT: Comment field is the wrong length");
return false;
}
}
@@ -285,7 +278,7 @@ namespace AZ::IO::ZipDir
|| m_CDREnd.nCDRStartDisk != 0
|| m_CDREnd.numEntriesOnDisk != m_CDREnd.numEntriesTotal)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_UNSUPPORTED, "Multivolume archive detected. Current version of ZipDir does not support multivolume archives");
AZ_Warning("Archive", false, "ZD_ERROR_UNSUPPORTED: Multivolume archive detected.Current version of ZipDir does not support multivolume archives");
return false;
}
@@ -295,7 +288,7 @@ namespace AZ::IO::ZipDir
|| m_CDREnd.lCDRSize > m_nCDREndPos
|| m_CDREnd.lCDROffset + m_CDREnd.lCDRSize > m_nCDREndPos)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "The central directory offset or size are out of range, the pak is probably corrupt, try to repare or delete the file");
AZ_Warning("Archive", false, "ZD_ERROR_DATA_IS_CORRUPT: The central directory offset or size are out of range, the pak is probably corrupt, try to repare or delete the file");
return false;
}
@@ -394,7 +387,12 @@ namespace AZ::IO::ZipDir
// if there's nothing to search
if (nNewBufPos >= nOldBufPos)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_NO_CDR, "Cannot find Central Directory Record in pak. This is either not a pak file, or a pak file without Central Directory. It does not mean that the data is permanently lost, but it may be severely damaged. Please repair the file with external tools, there may be enough information left to recover the file completely."); // we didn't find anything
AZ_Warning("Archive", false, "ZD_ERROR_NO_CDR: Cannot find Central Directory Record in pak."
" This is either not a pak file, or a pak file without Central Directory."
" It does not mean that the data is permanently lost,"
" but it may be severely damaged."
" Please repair the file with external tools,"
" there may be enough information left to recover the file completely."); // we didn't find anything
return false;
}
@@ -418,7 +416,11 @@ namespace AZ::IO::ZipDir
}
else
{
THROW_ZIPDIR_ERROR(ZD_ERROR_DATA_IS_CORRUPT, "Central Directory Record is followed by a comment of inconsistent length. This might be a minor misconsistency, please try to repair the file. However, it is dangerous to open the file because I will have to guess some structure offsets, which can lead to permanent unrecoverable damage of the archive content");
AZ_Warning("Archive", false, "ZD_ERROR_DATA_IS_CORRUPT:"
" Central Directory Record is followed by a comment of inconsistent length."
" This might be a minor misconsistency, please try to repair the file.However,"
" it is dangerous to open the file because I will have to guess some structure offsets,"
" which can lead to permanent unrecoverable damage of the archive content");
return false;
}
}
@@ -436,7 +438,7 @@ namespace AZ::IO::ZipDir
nOldBufPos = nNewBufPos;
memmove(&pReservedBuffer[CDRSearchWindowSize], pWindow, sizeof(ZipFile::CDREnd) - 1);
}
THROW_ZIPDIR_ERROR(ZD_ERROR_UNEXPECTED, "The program flow may not have possibly lead here. This error is unexplainable"); // we shouldn't be here
AZ_Assert(false, "ZD_ERROR_UNEXPECTED: The program flow may not have possibly lead here. This error is unexplainable"); // we shouldn't be here
return false;
}
@@ -460,13 +462,13 @@ namespace AZ::IO::ZipDir
if (pBuffer.empty()) // couldn't allocate enough memory for temporary copy of CDR
{
THROW_ZIPDIR_ERROR(ZD_ERROR_NO_MEMORY, "Not enough memory to cache Central Directory record for fast initialization. This error may not happen on non-console systems");
AZ_Warning("Archive", false, "ZD_ERROR_NO_MEMORY: Not enough memory to cache Central Directory record for fast initialization. This error may not happen on non-console systems");
return false;
}
if (!ReadHeaderData(&pBuffer[0], m_CDREnd.lCDRSize))
{
THROW_ZIPDIR_ERROR(ZD_ERROR_CORRUPTED_DATA, "Archive contains corrupted CDR.");
AZ_Warning("Archive", false, "ZD_ERROR_CORRUPTED_DATA: Archive contains corrupted CDR.");
return false;
}
@@ -482,7 +484,7 @@ namespace AZ::IO::ZipDir
if ((pFile->nVersionNeeded & 0xFF) > 20)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_UNSUPPORTED, "Cannot read the archive file (nVersionNeeded > 20).");
AZ_Warning("Archive", false, "ZD_ERROR_UNSUPPORTED: Cannot read the archive file (nVersionNeeded > 20).");
return false;
}
//if (pFile->lSignature != pFile->SIGNATURE) // Timur, Dont compare signatures as signatue in memory can be overwritten by the code below
@@ -492,7 +494,8 @@ namespace AZ::IO::ZipDir
// if the record overlaps with the End Of CDR structure, something is wrong
if (pEndOfRecord > pEndOfData)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_CDR_IS_CORRUPT, "Central Directory record is either corrupt, or truncated, or missing. Cannot read the archive directory");
AZ_Warning("Archive", false, "ZD_ERROR_CDR_IS_CORRUPT: Central Directory record is either corrupt, or truncated, or missing."
" Cannot read the archive directory");
return false;
}
@@ -555,13 +558,17 @@ namespace AZ::IO::ZipDir
{
if (pFileHeader->lLocalHeaderOffset > m_CDREnd.lCDROffset)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_CDR_IS_CORRUPT, "Central Directory contains file descriptors pointing outside the archive file boundaries. The archive file is either truncated or damaged. Please try to repair the file"); // the file offset is beyond the CDR: impossible
AZ_Warning("Archive", false, "ZD_ERROR_CDR_IS_CORRUPT:"
" Central Directory contains file descriptors pointing outside the archive file boundaries."
" The archive file is either truncated or damaged.Please try to repair the file"); // the file offset is beyond the CDR: impossible
return;
}
if ((pFileHeader->nMethod == ZipFile::METHOD_STORE || pFileHeader->nMethod == ZipFile::METHOD_STORE_AND_STREAMCIPHER_KEYTABLE) && pFileHeader->desc.lSizeUncompressed != pFileHeader->desc.lSizeCompressed)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_VALIDATION_FAILED, "File with STORE compression method declares its compressed size not matching its uncompressed size. File descriptor is inconsistent, archive content may be damaged, please try to repair the archive");
AZ_Warning("Archive", false, "ZD_ERROR_VALIDATION_FAILED:"
" File with STORE compression method declares its compressed size not matching its uncompressed size."
" File descriptor is inconsistent, archive content may be damaged, please try to repair the archive");
return;
}
@@ -617,7 +624,9 @@ namespace AZ::IO::ZipDir
//|| pFileHeader->nLastModTime != pLocalFileHeader->nLastModTime
)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_VALIDATION_FAILED, "The local file header descriptor doesn't match the basic parameters declared in the global file header in the file. The archive content is misconsistent and may be damaged. Please try to repair the archive");
AZ_Warning("Archive", false, "ZD_ERROR_VALIDATION_FAILED:"
" The local file header descriptor doesn't match the basic parameters declared in the global file header in the file."
" The archive content is misconsistent and may be damaged. Please try to repair the archive");
return;
}
@@ -628,7 +637,9 @@ namespace AZ::IO::ZipDir
if (!AZStd::equal(zipFileDataBegin, zipFileDataEnd, reinterpret_cast<const char*>(pFileHeader + 1), CompareNoCase))
{
// either file name, or the extra field do not match
THROW_ZIPDIR_ERROR(ZD_ERROR_VALIDATION_FAILED, "The local file header contains file name which does not match the file name of the global file header. The archive content is misconsistent with its directory. Please repair the archive");
AZ_Warning("Archive", false, "ZD_ERROR_VALIDATION_FAILED:"
" The local file header contains file name which does not match the file name of the global file header."
" The archive content is misconsistent with its directory. Please repair the archive");
return;
}
@@ -642,7 +653,9 @@ namespace AZ::IO::ZipDir
if (fileEntry.nFileDataOffset >= m_nCDREndPos)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_VALIDATION_FAILED, "The global file header declares the file which crosses the boundaries of the archive. The archive is either corrupted or truncated, please try to repair it");
AZ_Warning("Archive", false, "ZD_ERROR_VALIDATION_FAILED:"
" The global file header declares the file which crosses the boundaries of the archive."
" The archive is either corrupted or truncated, please try to repair it");
return;
}
@@ -686,29 +699,29 @@ namespace AZ::IO::ZipDir
case Z_OK:
break;
case Z_MEM_ERROR:
THROW_ZIPDIR_ERROR(ZD_ERROR_ZLIB_NO_MEMORY, "ZLib reported out-of-memory error");
AZ_Warning("Archive", false, "ZD_ERROR_ZLIB_NO_MEMORY: ZLib reported out-of-memory error");
return;
case Z_BUF_ERROR:
THROW_ZIPDIR_ERROR(ZD_ERROR_ZLIB_CORRUPTED_DATA, "ZLib reported compressed stream buffer error");
AZ_Warning("Archive", false, "ZD_ERROR_ZLIB_CORRUPTED_DATA: ZLib reported compressed stream buffer error");
return;
case Z_DATA_ERROR:
THROW_ZIPDIR_ERROR(ZD_ERROR_ZLIB_CORRUPTED_DATA, "ZLib reported compressed stream data error");
AZ_Warning("Archive", false, "ZD_ERROR_ZLIB_CORRUPTED_DATA: ZLib reported compressed stream data error");
return;
default:
THROW_ZIPDIR_ERROR(ZD_ERROR_ZLIB_FAILED, "ZLib reported an unexpected unknown error");
AZ_Warning("Archive", false, "ZD_ERROR_ZLIB_FAILED: ZLib reported an unexpected unknown error");
return;
}
if (nDestSize != fileEntry.desc.lSizeUncompressed)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_CORRUPTED_DATA, "Uncompressed stream doesn't match the size of uncompressed file stored in the archive file headers");
AZ_Warning("Archive", false, "ZD_ERROR_CORRUPTED_DATA: Uncompressed stream doesn't match the size of uncompressed file stored in the archive file headers");
return;
}
uLong uCRC32 = AZ::Crc32((Bytef*)pUncompressed, nDestSize);
if (uCRC32 != fileEntry.desc.lCRC32)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_CRC32_CHECK, "Uncompressed stream CRC32 check failed");
AZ_Warning("Archive", false, "ZD_ERROR_CRC32_CHECK: Uncompressed stream CRC32 check failed");
return;
}
}
@@ -737,7 +750,7 @@ namespace AZ::IO::ZipDir
{
if (FSeek(&m_fileExt, nPos, nOrigin))
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Cannot fseek() to the new position in the file. This is unexpected error and should not happen under any circumstances. Perhaps some network or disk failure error has caused this");
AZ_Warning("Archive", false, "ZD_ERROR_IO_FAILED: Cannot fseek() to the new position in the file. This is unexpected error and should not happen under any circumstances. Perhaps some network or disk failure error has caused this");
return;
}
}
@@ -747,7 +760,7 @@ namespace AZ::IO::ZipDir
int64_t nPos = FTell(&m_fileExt);
if (nPos == -1)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Cannot ftell() position in the archive. This is unexpected error and should not happen under any circumstances. Perhaps some network or disk failure error has caused this");
AZ_Warning("Archive", false, "ZD_ERROR_IO_FAILED: Cannot ftell() position in the archive. This is unexpected error and should not happen under any circumstances. Perhaps some network or disk failure error has caused this");
return 0;
}
return nPos;
@@ -757,7 +770,7 @@ namespace AZ::IO::ZipDir
{
if (FRead(&m_fileExt, pDest, nSize, 1) != 1)
{
THROW_ZIPDIR_ERROR(ZD_ERROR_IO_FAILED, "Cannot fread() a portion of data from archive");
AZ_Warning("Archive", false, "ZD_ERROR_IO_FAILED: Cannot fread() a portion of data from archive");
return false;
}
return true;
@@ -33,20 +33,13 @@ namespace AZ::IO::ZipDir
// if this is set, the archive will be created anew (the existing file will be overwritten)
FLAGS_CREATE_NEW = 1 << 3,
// Cache will be loaded completely into the memory.
FLAGS_IN_MEMORY = 1 << 4,
FLAGS_IN_MEMORY_CPU = 1 << 5,
// Store all file names as crc32 in a flat directory structure.
FLAGS_FILENAMES_AS_CRC32 = 1 << 6,
// if this is set, zip path will be searched inside other zips
FLAGS_READ_INSIDE_PAK = 1 << 7,
};
// initializes the internal structures
// nFlags can have FLAGS_READ_ONLY flag, in this case the object will be opened only for reading
CacheFactory (InitMethodEnum nInitMethod, uint32_t nFlags = 0);
CacheFactory(InitMethodEnum nInitMethod, uint32_t nFlags = 0);
~CacheFactory();
// the new function creates a new cache
@@ -17,7 +17,7 @@
namespace AZ::IO::ZipDir
{
bool FindFile::FindFirst(AZStd::string_view szWildcard)
bool FindFile::FindFirst(AZ::IO::PathView szWildcard)
{
if (!PreFind(szWildcard))
{
@@ -29,7 +29,7 @@ namespace AZ::IO::ZipDir
return SkipNonMatchingFiles();
}
bool FindDir::FindFirst(AZStd::string_view szWildcard)
bool FindDir::FindFirst(AZ::IO::PathView szWildcard)
{
if (!PreFind(szWildcard))
{
@@ -42,37 +42,20 @@ namespace AZ::IO::ZipDir
}
// matches the file wildcard in the m_szWildcard to the given file/dir name
// this takes into account the fact that xxx. is the alias name for xxx
bool FindData::MatchWildcard(AZStd::string_view szName)
bool FindData::MatchWildcard(AZ::IO::PathView szName)
{
if (AZStd::wildcard_match(m_szWildcard, szName))
{
return true;
}
// check if the file object name contains extension sign (.)
size_t extensionOffset = szName.find('.');
if (extensionOffset != AZStd::string_view::npos)
{
return false;
}
// no extension sign - add it
AZStd::fixed_string<AZ_MAX_PATH_LEN> szAlias{ szName };
szAlias.push_back('.');
return AZStd::wildcard_match(m_szWildcard, szAlias);
return szName.Match(m_szWildcard.Native());
}
FileEntry* FindFile::FindExact(AZStd::string_view szPath)
FileEntry* FindFile::FindExact(AZ::IO::PathView szPath)
{
if (!PreFind(szPath))
{
return nullptr;
}
FileEntryTree::FileMap::iterator itFile = m_pDirHeader->FindFile(m_szWildcard.c_str());
FileEntryTree::FileMap::iterator itFile = m_pDirHeader->FindFile(m_szWildcard);
if (itFile == m_pDirHeader->GetFileEnd())
{
m_pDirHeader = nullptr; // we didn't find it, fail the search
@@ -84,7 +67,7 @@ namespace AZ::IO::ZipDir
return m_pDirHeader->GetFileEntry(m_itFile);
}
FileEntryTree* FindDir::FindExact(AZStd::string_view szPath)
FileEntryTree* FindDir::FindExact(AZ::IO::PathView szPath)
{
if (!PreFind(szPath))
{
@@ -97,40 +80,50 @@ namespace AZ::IO::ZipDir
//////////////////////////////////////////////////////////////////////////
// after this call returns successfully (with true returned), the m_szWildcard
// contains the file name/wildcard and m_pDirHeader contains the directory where
// contains the file name/glob and m_pDirHeader contains the directory where
// the file (s) are to be found
bool FindData::PreFind(AZStd::string_view szWildcard)
bool FindData::PreFind(AZ::IO::PathView pathGlob)
{
if (!m_pRoot)
{
return false;
}
// start the search from the root
m_pDirHeader = m_pRoot;
m_szWildcard = szWildcard;
// for each path directory, copy it into the wildcard buffer and try to find the subdirectory
for (AZStd::optional<AZStd::string_view> pathEntry = AZ::StringFunc::TokenizeNext(szWildcard, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR); pathEntry;
pathEntry = AZ::StringFunc::TokenizeNext(szWildcard, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR))
FileEntryTree* entryTreeHeader = m_pRoot;
// If there is a root path in the glob path, attempt to locate it from the root
if (AZ::IO::PathView rootPath = m_szWildcard.RootPath(); !rootPath.empty())
{
// Update wildcard to new path entry
m_szWildcard = *pathEntry;
// If the wildcard parameter that has been passed to TokenizeNext is empty
// Then pathEntry is the final portion of the path
if (!szWildcard.empty())
FileEntryTree* dirEntry = entryTreeHeader->FindDir(rootPath);
if (dirEntry == nullptr)
{
FileEntryTree* dirEntry = m_pDirHeader->FindDir(*pathEntry);
if (!dirEntry)
{
m_pDirHeader = nullptr; // an intermediate directory has not been found continue the search
return false;
}
m_pDirHeader = dirEntry->GetDirectory();
return false;
}
entryTreeHeader = dirEntry->GetDirectory();
pathGlob = pathGlob.RelativePath();
}
AZ::IO::PathView filenameSegment = pathGlob;
// Recurse through the directories within the file tree for each remaining parent path segment
// of pathGlob parameter
auto parentPathIter = pathGlob.begin();
for (auto filenamePathIter = parentPathIter == pathGlob.end() ? pathGlob.end() : AZStd::next(parentPathIter, 1);
filenamePathIter != pathGlob.end(); ++parentPathIter, ++filenamePathIter)
{
FileEntryTree* dirEntry = entryTreeHeader->FindDir(*parentPathIter);
if (dirEntry == nullptr)
{
return false;
}
entryTreeHeader = dirEntry->GetDirectory();
filenameSegment = *filenamePathIter;
}
// At this point the all the intermediate directories have been found
// so update the directory header to point at the last file entry tree
m_pDirHeader = entryTreeHeader;
m_szWildcard = filenameSegment;
return true;
}
@@ -38,11 +38,10 @@ namespace AZ::IO::ZipDir
// after this call returns successfully (with true returned), the m_szWildcard
// contains the file name/wildcard and m_pDirHeader contains the directory where
// the file (s) are to be found
bool PreFind(AZStd::string_view szWildcard);
bool PreFind(AZ::IO::PathView szWildcard);
// matches the file wildcard in the m_szWildcard to the given file/dir name
// this takes into account the fact that xxx. is the alias name for xxx
bool MatchWildcard(AZStd::string_view szName);
bool MatchWildcard(AZ::IO::PathView szName);
// the directory inside which the current object (file or directory) is being searched
FileEntryTree* m_pDirHeader{};
@@ -50,7 +49,7 @@ namespace AZ::IO::ZipDir
FileEntryTree* m_pRoot{}; // the root of the zip file in which to search
// the actual wildcard being used in the current scan - the file name wildcard only!
AZStd::fixed_string<AZ_MAX_PATH_LEN> m_szWildcard;
AZ::IO::FixedMaxPath m_szWildcard;
};
class FindFile
@@ -66,9 +65,9 @@ namespace AZ::IO::ZipDir
{
}
// if bExactFile is passed, only the file is searched, and besides with the exact name as passed (no wildcards)
bool FindFirst(AZStd::string_view szWildcard);
bool FindFirst(AZ::IO::PathView szWildcard);
FileEntry* FindExact(AZStd::string_view szPath);
FileEntry* FindExact(AZ::IO::PathView szPath);
// goes on to the next file entry
bool FindNext();
@@ -94,9 +93,9 @@ namespace AZ::IO::ZipDir
{
}
// if bExactFile is passed, only the file is searched, and besides with the exact name as passed (no wildcards)
bool FindFirst(AZStd::string_view szWildcard);
bool FindFirst(AZ::IO::PathView szWildcard);
FileEntryTree* FindExact(AZStd::string_view szPath);
FileEntryTree* FindExact(AZ::IO::PathView szPath);
// goes on to the next file entry
bool FindNext();
@@ -68,14 +68,14 @@ namespace AZ::IO::ZipDir
{
for (FileEntryTree::SubdirMap::iterator it = pTree->GetDirBegin(); it != pTree->GetDirEnd(); ++it)
{
AddAllFiles(it->second.get(), AZStd::string::format("%.*s%.*s/", aznumeric_cast<int>(strRoot.size()), strRoot.data(), aznumeric_cast<int>(it->first.size()), it->first.data()));
AddAllFiles(it->second.get(), (AZ::IO::Path(strRoot) / it->first).Native());
}
for (FileEntryTree::FileMap::iterator it = pTree->GetFileBegin(); it != pTree->GetFileEnd(); ++it)
{
FileRecord rec;
rec.pFileEntryBase = pTree->GetFileEntry(it);
rec.strPath = AZStd::string::format("%.*s%.*s", aznumeric_cast<int>(strRoot.size()), strRoot.data(), aznumeric_cast<int>(it->first.size()), it->first.data());
rec.strPath = (AZ::IO::Path(strRoot) / it->first).Native();
push_back(rec);
}
}
@@ -432,18 +432,18 @@ namespace AZ::IO::ZipDir
bool CZipFile::EvaluateSectorSize(const char* filename)
{
char volume[AZ_MAX_PATH_LEN];
AZ::IO::FixedMaxPath volume;
if (AZ::StringFunc::Path::IsRelative(filename))
if (AZ::IO::PathView(filename).IsRelative())
{
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(filename, volume, AZ_ARRAY_SIZE(volume));
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(volume, filename);
}
else
{
azstrcpy(volume, AZ_ARRAY_SIZE(volume), filename);
volume = filename;
}
AZ::IO::FixedMaxPathString drive{ AZ::IO::PathView(volume).RootName().Native() };
AZ::IO::FixedMaxPath drive = volume.RootName();
if (drive.empty())
{
return false;
@@ -666,7 +666,9 @@ namespace AZ::IO::ZipDir
DirEntry* pEnd = pBegin + this->numDirs;
DirEntry* pEntry = AZStd::lower_bound(pBegin, pEnd, szName, pred);
#if AZ_TRAIT_LEGACY_CRYPAK_UNIX_LIKE_FILE_SYSTEM
if (pEntry != pEnd && !azstrnicmp(szName.data(), pEntry->GetName(pNamePool), szName.size()))
AZ::IO::PathView searchPath(szName, AZ::IO::WindowsPathSeparator);
AZ::IO::PathView entryPath(pEntry->GetName(pNamePool), AZ::IO::WindowsPathSeparator);
if (pEntry != pEnd && searchPath == entryPath)
#else
if (pEntry != pEnd && szName == pEntry->GetName(pNamePool))
#endif
@@ -690,7 +692,9 @@ namespace AZ::IO::ZipDir
FileEntry* pEnd = pBegin + this->numFiles;
FileEntry* pEntry = AZStd::lower_bound(pBegin, pEnd, szName, pred);
#if AZ_TRAIT_LEGACY_CRYPAK_UNIX_LIKE_FILE_SYSTEM
if (pEntry != pEnd && !azstrnicmp(szName.data(), pEntry->GetName(pNamePool), szName.size()))
AZ::IO::PathView searchPath(szName, AZ::IO::WindowsPathSeparator);
AZ::IO::PathView entryPath(pEntry->GetName(pNamePool), AZ::IO::WindowsPathSeparator);
if (pEntry != pEnd && searchPath == entryPath)
#else
if (pEntry != pEnd && szName == pEntry->GetName(pNamePool))
#endif
@@ -990,13 +994,6 @@ namespace AZ::IO::ZipDir
}
//////////////////////////////////////////////////////////////////////////
uint32_t FileNameHash(AZStd::string_view filename)
{
AZ::IO::StackString pathname{ filename };
AZStd::replace(AZStd::begin(pathname), AZStd::end(pathname), AZ_WRONG_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR);
return AZ::Crc32(pathname);
}
int64_t FSeek(CZipFile* file, int64_t origin, int command)
{
@@ -119,8 +119,6 @@ namespace AZ::IO::ZipDir
const char* m_szDescription;
};
#define THROW_ZIPDIR_ERROR(ZD_ERR, DESC) AZ_Warning("Archive", false, DESC)
// possible initialization methods
enum InitMethodEnum
{
@@ -157,8 +155,6 @@ namespace AZ::IO::ZipDir
int FEof(CZipFile* zipFile);
uint32_t FileNameHash(AZStd::string_view filename);
//////////////////////////////////////////////////////////////////////////
struct SExtraZipFileData
@@ -9,7 +9,6 @@
#include <AzCore/Console/Console.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/Archive/ZipFileFormat.h>
#include <AzFramework/Archive/ZipDirStructures.h>
#include <AzFramework/Archive/ZipDirTree.h>
@@ -18,37 +17,42 @@ namespace AZ::IO::ZipDir
{
// Adds or finds the file. Returns non-initialized structure if it was added,
// or an IsInitialized() structure if it was found
FileEntry* FileEntryTree::Add(AZStd::string_view szPath)
FileEntry* FileEntryTree::Add(AZ::IO::PathView inputPathView)
{
AZStd::optional<AZStd::string_view> pathEntry = AZ::StringFunc::TokenizeNext(szPath, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR);
if (!pathEntry)
if (inputPathView.empty())
{
AZ_Assert(false, "An empty file path cannot be added to the zip file entry tree");
return nullptr;
}
// If a path separator was found, add a subdirectory
if (!szPath.empty())
auto inputPathIter = inputPathView.begin();
AZ::IO::PathView firstPathSegment(*inputPathIter);
auto inputPathNextIter = inputPathIter == inputPathView.end() ? inputPathView.end() : AZStd::next(inputPathIter, 1);
AZ::IO::PathView remainingPath = inputPathNextIter != inputPathView.end() ?
AZStd::string_view(inputPathNextIter->Native().begin(), inputPathView.Native().end())
: AZStd::string_view{};
if (!remainingPath.empty())
{
auto dirEntryIter = m_mapDirs.find(*pathEntry);
auto dirEntryIter = m_mapDirs.find(firstPathSegment);
// we have a subdirectory here - create the file in it
if (dirEntryIter == m_mapDirs.end())
{
dirEntryIter = m_mapDirs.emplace(*pathEntry, AZStd::make_unique<FileEntryTree>()).first;
dirEntryIter = m_mapDirs.emplace(firstPathSegment, AZStd::make_unique<FileEntryTree>()).first;
}
return dirEntryIter->second->Add(szPath);
return dirEntryIter->second->Add(remainingPath);
}
// Add the filename
auto fileEntryIter = m_mapFiles.find(*pathEntry);
auto fileEntryIter = m_mapFiles.find(firstPathSegment);
if (fileEntryIter == m_mapFiles.end())
{
fileEntryIter = m_mapFiles.emplace(*pathEntry, AZStd::make_unique<FileEntry>()).first;
fileEntryIter = m_mapFiles.emplace(firstPathSegment, AZStd::make_unique<FileEntry>()).first;
}
return fileEntryIter->second.get();
}
// adds a file to this directory
ErrorEnum FileEntryTree::Add(AZStd::string_view szPath, const FileEntryBase& file)
ErrorEnum FileEntryTree::Add(AZ::IO::PathView szPath, const FileEntryBase& file)
{
FileEntry* pFile = Add(szPath);
if (!pFile)
@@ -63,7 +67,7 @@ namespace AZ::IO::ZipDir
return ZD_ERROR_SUCCESS;
}
// returns the number of files in this tree, including this and sublevels
// returns the number of files in this tree, including this and subdirectories
uint32_t FileEntryTree::NumFilesTotal() const
{
uint32_t numFiles = aznumeric_cast<uint32_t>(m_mapFiles.size());
@@ -91,21 +95,6 @@ namespace AZ::IO::ZipDir
m_mapFiles.clear();
}
size_t FileEntryTree::GetSize() const
{
size_t nSize = sizeof(*this);
for (const auto& [dirname, dirEntry] : m_mapDirs)
{
nSize += dirname.size() + sizeof(decltype(m_mapDirs)::value_type) + dirEntry->GetSize();
}
for (const auto& [filename, fileEntry] : m_mapFiles)
{
nSize += filename.size() + sizeof(decltype(m_mapFiles)::value_type);
}
return nSize;
}
bool FileEntryTree::IsOwnerOf(const FileEntry* pFileEntry) const
{
for (const auto& [path, fileEntry] : m_mapFiles)
@@ -127,7 +116,7 @@ namespace AZ::IO::ZipDir
return false;
}
FileEntryTree* FileEntryTree::FindDir(AZStd::string_view szDirName)
FileEntryTree* FileEntryTree::FindDir(AZ::IO::PathView szDirName)
{
if (auto it = m_mapDirs.find(szDirName); it != m_mapDirs.end())
{
@@ -137,7 +126,7 @@ namespace AZ::IO::ZipDir
return nullptr;
}
FileEntryTree::FileMap::iterator FileEntryTree::FindFile(AZStd::string_view szFileName)
FileEntryTree::FileMap::iterator FileEntryTree::FindFile(AZ::IO::PathView szFileName)
{
return m_mapFiles.find(szFileName);
}
@@ -152,7 +141,7 @@ namespace AZ::IO::ZipDir
return it == GetDirEnd() ? nullptr : it->second.get();
}
ErrorEnum FileEntryTree::RemoveDir(AZStd::string_view szDirName)
ErrorEnum FileEntryTree::RemoveDir(AZ::IO::PathView szDirName)
{
SubdirMap::iterator itRemove = m_mapDirs.find(szDirName);
if (itRemove == m_mapDirs.end())
@@ -164,7 +153,13 @@ namespace AZ::IO::ZipDir
return ZD_ERROR_SUCCESS;
}
ErrorEnum FileEntryTree::RemoveFile(AZStd::string_view szFileName)
ErrorEnum FileEntryTree::RemoveAll()
{
Clear();
return ZD_ERROR_SUCCESS;
}
ErrorEnum FileEntryTree::RemoveFile(AZ::IO::PathView szFileName)
{
FileMap::iterator itRemove = m_mapFiles.find(szFileName);
if (itRemove == m_mapFiles.end())
@@ -10,6 +10,7 @@
#pragma once
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/string/string_view.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
@@ -24,12 +25,12 @@ namespace AZ::IO::ZipDir
// adds a file to this directory
// Function can modify szPath input
ErrorEnum Add(AZStd::string_view szPath, const FileEntryBase& file);
ErrorEnum Add(AZ::IO::PathView szPath, const FileEntryBase& file);
// Adds or finds the file. Returns non-initialized structure if it was added,
// or an IsInitialized() structure if it was found
// Function can modify szPath input
FileEntry* Add(AZStd::string_view szPath);
FileEntry* Add(AZ::IO::PathView szPath);
// returns the number of files in this tree, including this and sublevels
uint32_t NumFilesTotal() const;
@@ -45,24 +46,18 @@ namespace AZ::IO::ZipDir
m_mapFiles.swap(rThat.m_mapFiles);
}
size_t GetSize() const;
bool IsOwnerOf(const FileEntry* pFileEntry) const;
// subdirectories
using SubdirMap = AZStd::map<AZStd::string_view, AZStd::unique_ptr<FileEntryTree>>;
using SubdirMap = AZStd::map<AZ::IO::PathView, AZStd::unique_ptr<FileEntryTree>>;
// file entries
using FileMap = AZStd::map<AZStd::string_view, AZStd::unique_ptr<FileEntry>>;
using FileMap = AZStd::map<AZ::IO::PathView, AZStd::unique_ptr<FileEntry>>;
FileEntryTree* FindDir(AZStd::string_view szDirName);
ErrorEnum RemoveDir (AZStd::string_view szDirName);
ErrorEnum RemoveAll ()
{
Clear();
return ZD_ERROR_SUCCESS;
}
FileMap::iterator FindFile(AZStd::string_view szFileName);
ErrorEnum RemoveFile(AZStd::string_view szFileName);
FileEntryTree* FindDir(AZ::IO::PathView szDirName);
ErrorEnum RemoveDir(AZ::IO::PathView szDirName);
ErrorEnum RemoveAll();
FileMap::iterator FindFile(AZ::IO::PathView szFileName);
ErrorEnum RemoveFile(AZ::IO::PathView szFileName);
// the FileEntryTree is simultaneously an entry in the dir list AND the directory header
FileEntryTree* GetDirectory()
{
@@ -75,8 +70,8 @@ namespace AZ::IO::ZipDir
SubdirMap::iterator GetDirBegin() { return m_mapDirs.begin(); }
SubdirMap::iterator GetDirEnd() { return m_mapDirs.end(); }
uint32_t NumDirs() const { return aznumeric_cast<uint32_t>(m_mapDirs.size()); }
AZStd::string_view GetFileName(FileMap::iterator it) { return it->first; }
AZStd::string_view GetDirName(SubdirMap::iterator it) { return it->first; }
AZStd::string_view GetFileName(FileMap::iterator it) { return it->first.Native(); }
AZStd::string_view GetDirName(SubdirMap::iterator it) { return it->first.Native(); }
FileEntry* GetFileEntry(FileMap::iterator it);
FileEntryTree* GetDirEntry(SubdirMap::iterator it);
@@ -16,6 +16,7 @@
#include <AzFramework/Components/AzFrameworkConfigurationSystemComponent.h>
#include <AzFramework/Entity/GameEntityContextComponent.h>
#include <AzFramework/FileTag/FileTagComponent.h>
#include <AzFramework/Input/Contexts/InputContextComponent.h>
#include <AzFramework/Input/System/InputSystemComponent.h>
#include <AzFramework/Render/GameIntersectorComponent.h>
#include <AzFramework/Scene/SceneSystemComponent.h>
@@ -47,6 +48,7 @@ namespace AzFramework
AzFramework::CreateScriptDebugAgentFactory(),
AzFramework::AssetSystem::AssetSystemComponent::CreateDescriptor(),
AzFramework::InputSystemComponent::CreateDescriptor(),
AzFramework::InputContextComponent::CreateDescriptor(),
#if !defined(AZCORE_EXCLUDE_LUA)
AzFramework::ScriptComponent::CreateDescriptor(),
@@ -734,7 +734,7 @@ namespace AZ
{
if (AZ::StringFunc::StartsWith(pathStrView, aliasKey))
{
// Reduce of the size result result path by the size of the and add the resolved alias size
// Add to the size of result path by the resolved alias length - the alias key length
AZStd::string_view postAliasView = pathStrView.substr(aliasKey.size());
size_t requiredFixedMaxPathSize = postAliasView.size();
requiredFixedMaxPathSize += aliasValue.size();
@@ -55,6 +55,10 @@ namespace AzFramework
// Allocator
AZ_CLASS_ALLOCATOR(InputContext, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(InputContext, "{D17A85B2-405F-40AB-BBA7-F118256D39AB}", InputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] name Unique, will be truncated if exceeds InputDeviceId::MAX_NAME_LENGTH = 64
@@ -0,0 +1,172 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzFramework/Input/Contexts/InputContextComponent.h>
#include <AzFramework/Input/Mappings/InputMappingAnd.h>
#include <AzFramework/Input/Mappings/InputMappingOr.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("InputContextService", 0xa2734425));
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<InputContextComponent, AZ::Component>()
->Version(0)
->Field("Unique Name", &InputContextComponent::m_uniqueName)
->Field("Input Mappings", &InputContextComponent::m_inputMappings)
->Field("Local Player Index", &InputContextComponent::m_localPlayerIndex)
->Field("Input Listener Priority", &InputContextComponent::m_inputListenerPriority)
->Field("Consumes Processed Input", &InputContextComponent::m_consumesProcessedInput)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<InputContextComponent>("Input Context",
"An input context is a collection of input mappings, which map 'raw' input to custom input channels (ie. events).")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Category, "Input")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->DataElement(AZ::Edit::UIHandlers::Default, &InputContextComponent::m_uniqueName, "Unique Name",
"The name of the input context, unique among all active input contexts and input devices.\n"
"This will be truncated if its length exceeds that of InputDeviceId::MAX_NAME_LENGTH = 64")
->DataElement(AZ::Edit::UIHandlers::Default, &InputContextComponent::m_inputMappings, "Input Mappings",
"The list of all input mappings that will be created when the input context is activated.")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::SpinBox, &InputContextComponent::m_localPlayerIndex, "Local Player Index",
"The local player index that this context will receive input from (0 based, -1 means all controllers).\n"
"Will only work on platforms such as PC where the local user id corresponds to the local player index.\n"
"For other platforms, SetLocalUserId must be called at runtime with the id of a logged in user.")
->Attribute(AZ::Edit::Attributes::Min, -1)
->Attribute(AZ::Edit::Attributes::Max, 3)
->DataElement(AZ::Edit::UIHandlers::SpinBox, &InputContextComponent::m_inputListenerPriority, "Input Listener Priority",
"The priority used to sort the input context relative to all other input event listeners.\n"
"Higher numbers indicate greater priority.")
->Attribute(AZ::Edit::Attributes::Min, InputChannelEventListener::GetPriorityLast())
->Attribute(AZ::Edit::Attributes::Max, InputChannelEventListener::GetPriorityFirst())
->DataElement(AZ::Edit::UIHandlers::CheckBox, &InputContextComponent::m_consumesProcessedInput, "Consumes Processed Input",
"Should the input context consume input that is processed by any of its input mappings?")
;
}
}
InputMapping::ConfigBase::Reflect(context);
InputMappingAnd::Config::Reflect(context);
InputMappingOr::Config::Reflect(context);
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputContextComponent::~InputContextComponent()
{
Deactivate();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::Init()
{
// The local player index that this component will receive input from (0 base, -1 wildcard)
// can be set from data, but will only work on platforms where the local user id corresponds
// to a local player index. For other platforms SetLocalUserId must be called at runtime with
// the id of a logged in local user, which will overwrite anything that is set here from data.
const LocalUserId localUserId = (m_localPlayerIndex == -1) ? LocalUserIdAny : aznumeric_cast<AZ::u32>(m_localPlayerIndex);
SetLocalUserId(localUserId);
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::Activate()
{
InputContextComponentRequestBus::Handler::BusConnect(GetEntityId());
CreateInputContext();
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::Deactivate()
{
ResetInputContext();
InputContextComponentRequestBus::Handler::BusDisconnect(GetEntityId());
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::SetLocalUserId(LocalUserId localUserId)
{
// Create a new filter, or reset any existing one if we have been passed LocalUserIdAny.
if (localUserId != LocalUserIdAny)
{
m_localUserIdFilter = AZStd::make_shared<InputChannelEventFilterInclusionList>(InputChannelEventFilter::AnyChannelNameCrc32,
InputChannelEventFilter::AnyDeviceNameCrc32,
aznumeric_cast<AZ::u32>(m_localPlayerIndex));
}
else
{
m_localUserIdFilter.reset();
}
// Set the filter if the input context has already been created.
if (m_inputContext)
{
m_inputContext->SetFilter(m_localUserIdFilter);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::CreateInputContext()
{
if (m_uniqueName.empty())
{
AZ_Error("InputContextComponent", false, "Cannot create input context with empty name.");
return;
}
if (InputDeviceRequests::FindInputDevice(InputDeviceId(m_uniqueName.c_str())))
{
AZ_Error("InputContextComponent", false,
"Cannot create input context '%s' with non-unique name.", m_uniqueName.c_str());
return;
}
if (m_inputMappings.empty())
{
AZ_Error("InputContextComponent", false,
"Cannot create input context '%s' with no input mappings.", m_uniqueName.c_str());
return;
}
// Create the input context.
InputContext::InitData initData;
initData.autoActivate = true;
initData.filter = m_localUserIdFilter;
initData.priority = m_inputListenerPriority;
initData.consumesProcessedInput = m_consumesProcessedInput;
m_inputContext = AZStd::make_unique<InputContext>(m_uniqueName.c_str(), initData);
// Create and add all input mappings.
for (const InputMapping::ConfigBase* inputMapping : m_inputMappings)
{
inputMapping->CreateInputMappingAndAddToContext(*m_inputContext);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputContextComponent::ResetInputContext()
{
m_inputContext.reset();
}
} // namespace AzFramework
@@ -0,0 +1,129 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzFramework/Input/Contexts/InputContext.h>
#include <AzFramework/Input/Mappings/InputMapping.h>
#include <AzFramework/Input/User/LocalUserId.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
class InputContextComponentRequests : public AZ::ComponentBus
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! Set the local user id that the InputContextComponent should process input from
//! \param[in] localUserId Local user id the InputContextComponent should process input from
virtual void SetLocalUserId(LocalUserId localUserId) = 0;
};
using InputContextComponentRequestBus = AZ::EBus<InputContextComponentRequests>;
////////////////////////////////////////////////////////////////////////////////////////////////
//! An InputContextComponent is used to configure (at edit time) the data necessary to create an
//! InputContext (at run time). The life cycle of any InputContextComponent is controlled by the
//! AZ::Entity it is attached to, adhering to the same rules as any other AZ::Component, and the
//! InputContext which it owns is created/destroyed when the component is activated/deactivated.
class InputContextComponent : public AZ::Component
, public InputContextComponentRequestBus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// AZ::Component Setup
AZ_COMPONENT(InputContextComponent, "{321689F8-A572-47D7-9D1C-EF9E0D2CD472}");
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::ComponentDescriptor::GetProvidedServices
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::ComponentDescriptor::Reflect
static void Reflect(AZ::ReflectContext* context);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default Constructor
InputContextComponent() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~InputContextComponent() override;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::Component::Init
void Init() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::Component::Activate
void Activate() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AZ::Component::Deactivate
void Deactivate() override;
////////////////////////////////////////////////////////////////////////////////////////////
// \ref AzFramework::InputContextComponentRequests::SetLocalUserId
void SetLocalUserId(LocalUserId localUserId) override;
private:
////////////////////////////////////////////////////////////////////////////////////////////
//! Create the input context.
void CreateInputContext();
////////////////////////////////////////////////////////////////////////////////////////////
//! Reset the input context.
void ResetInputContext();
////////////////////////////////////////////////////////////////////////////////////////////
//! The list of all input mappings that will be created when the input context is activated.
//! Reflected to EditContext, then used to create and add input mapping classes in Activate.
AZStd::vector<InputMapping::ConfigBase*> m_inputMappings;
////////////////////////////////////////////////////////////////////////////////////////////
//! The name of the input context, unique among all active input contexts and input devices.
//! This will be truncated if its length exceeds that of InputDeviceId::MAX_NAME_LENGTH = 64
//! Reflected to EditContext, then used to create the unique input context class in Activate.
AZStd::string m_uniqueName;
////////////////////////////////////////////////////////////////////////////////////////////
//! Input context that is created and owned by this component. Not reflected to EditContext.
AZStd::unique_ptr<InputContext> m_inputContext;
////////////////////////////////////////////////////////////////////////////////////////////
//! Filter used to determine whether an input event should be handled by this input context.
//! Not reflected, but created inside SetLocalUserId if needed to fliter by a local user id.
AZStd::shared_ptr<InputChannelEventFilterInclusionList> m_localUserIdFilter;
////////////////////////////////////////////////////////////////////////////////////////////
//! The local player index that this component will receive input from (0 base, -1 wildcard).
//! Will only work on platforms where the local user id corresponds to the local player index.
//! For other platforms, SetLocalUserId must be called at runtime with id of a logged in user.
//! Reflected to EditContext, then used if needed to create the local user id filter in Init.
AZ::s32 m_localPlayerIndex = -1;
////////////////////////////////////////////////////////////////////////////////////////////
//! The priority used to sort the input context relative to all other input event listeners.
//! Reflected to EditContext, then used to create the unique input context class in Activate.
AZ::s32 m_inputListenerPriority = InputChannelEventListener::GetPriorityDefault();
////////////////////////////////////////////////////////////////////////////////////////////
//! Should the input context consume input that is processed by any of its input mappings?
//! Reflected to EditContext, then used to create the unique input context class in Activate.
bool m_consumesProcessedInput = false;
};
} // namespace AzFramework
@@ -9,9 +9,156 @@
#include <AzFramework/Input/Mappings/InputMapping.h>
#include <AzFramework/Input/Contexts/InputContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/sort.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
void InputMapping::InputChannelNameFilteredByDeviceType::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<InputMapping::InputChannelNameFilteredByDeviceType>()
->Version(0)
->Field("Input Device Type", &InputChannelNameFilteredByDeviceType::m_inputDeviceType)
->Field("Input Channel Name", &InputChannelNameFilteredByDeviceType::m_inputChannelName)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<InputChannelNameFilteredByDeviceType>("InputChannelNameFilteredByDeviceType",
"An input channel name (filtered by an input device type) to add to the input mapping.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &InputChannelNameFilteredByDeviceType::GetNameLabelOverride)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &InputChannelNameFilteredByDeviceType::m_inputDeviceType, "Input Device Type",
"The type of input device by which to filter input channel names.")
->Attribute(AZ::Edit::Attributes::StringList, &InputChannelNameFilteredByDeviceType::GetValidInputDeviceTypes)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &InputChannelNameFilteredByDeviceType::OnInputDeviceTypeSelected)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &InputChannelNameFilteredByDeviceType::m_inputChannelName, "Input Channel Name",
"The input channel name to add to the input mapping.")
->Attribute(AZ::Edit::Attributes::StringList, &InputChannelNameFilteredByDeviceType::GetValidInputChannelNamesBySelectedDevice)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputMapping::InputChannelNameFilteredByDeviceType::InputChannelNameFilteredByDeviceType()
{
// Try initialize the selected input device type and input channel name to something valid.
if (m_inputDeviceType.empty())
{
const AZStd::vector<AZStd::string> validInputDeviceTypes = GetValidInputDeviceTypes();
if (!validInputDeviceTypes.empty())
{
m_inputDeviceType = validInputDeviceTypes[0];
OnInputDeviceTypeSelected();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZ::Crc32 InputMapping::InputChannelNameFilteredByDeviceType::OnInputDeviceTypeSelected()
{
const AZStd::vector<AZStd::string> validInputNames = GetValidInputChannelNamesBySelectedDevice();
if (!validInputNames.empty())
{
m_inputChannelName = validInputNames[0];
}
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string InputMapping::InputChannelNameFilteredByDeviceType::GetNameLabelOverride() const
{
return m_inputChannelName.empty() ? "<Select>" : m_inputChannelName;
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::vector<AZStd::string> InputMapping::InputChannelNameFilteredByDeviceType::GetValidInputDeviceTypes() const
{
AZStd::set<AZStd::string> uniqueInputDeviceTypes;
InputDeviceRequests::InputDeviceByIdMap availableInputDevicesById;
InputDeviceRequestBus::Broadcast(&InputDeviceRequests::GetInputDevicesById,
availableInputDevicesById);
for (const auto& inputDeviceById : availableInputDevicesById)
{
// Filter out input contexts so that mappings can only be created from 'raw' input events.
if (!azrtti_istypeof<InputContext*>(inputDeviceById.second))
{
uniqueInputDeviceTypes.insert(inputDeviceById.first.GetName());
}
}
return AZStd::vector<AZStd::string>(uniqueInputDeviceTypes.begin(), uniqueInputDeviceTypes.end());
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::vector<AZStd::string> InputMapping::InputChannelNameFilteredByDeviceType::GetValidInputChannelNamesBySelectedDevice() const
{
AZStd::vector<AZStd::string> validInputChannelNames;
InputDeviceId selectedDeviceId(m_inputDeviceType.c_str());
InputDeviceRequests::InputChannelIdSet validInputChannelIds;
InputDeviceRequestBus::Event(selectedDeviceId,
&InputDeviceRequests::GetInputChannelIds,
validInputChannelIds);
for (const InputChannelId& inputChannelId : validInputChannelIds)
{
validInputChannelNames.push_back(inputChannelId.GetName());
}
AZStd::sort(validInputChannelNames.begin(), validInputChannelNames.end());
return validInputChannelNames;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void InputMapping::ConfigBase::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<InputMapping::ConfigBase>()
->Version(0)
->Field("Output Input Channel Name", &InputMapping::ConfigBase::m_outputInputChannelName)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<InputMapping::ConfigBase>("Input Mapping: Base",
"Maps multiple different input sources to a single output input channel.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &ConfigBase::m_outputInputChannelName, "Output Input Channel Name",
"The unique input channel name (ie. input event name) output by the input mapping.\n"
"This will be truncated if its length exceeds that of InputChannelId::MAX_NAME_LENGTH = 64")
->Attribute(AZ::Edit::Attributes::Max, InputChannelId::MAX_NAME_LENGTH)
;
}
}
InputChannelNameFilteredByDeviceType::Reflect(context);
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::shared_ptr<InputMapping> InputMapping::ConfigBase::CreateInputMappingAndAddToContext(InputContext& inputContext) const
{
AZStd::shared_ptr<InputMapping> inputMapping = CreateInputMapping(inputContext);
if (inputMapping)
{
inputContext.AddInputMapping(inputMapping);
}
return inputMapping;
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string InputMapping::ConfigBase::GetNameLabelOverride() const
{
return m_outputInputChannelName.empty() ? "<Input Mapping>" : m_outputInputChannelName;
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputMapping::InputMapping(const InputChannelId& inputChannelId, const InputContext& inputContext)
: InputChannel(inputChannelId, inputContext)
@@ -12,6 +12,7 @@
#include <AzFramework/Input/Devices/InputDevice.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
@@ -26,6 +27,111 @@ namespace AzFramework
class InputMapping : public InputChannel
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience class that allows for selection of an input channel name filtered by device.
struct InputChannelNameFilteredByDeviceType
{
public:
////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputChannelNameFilteredByDeviceType, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(InputChannelNameFilteredByDeviceType, "{68CC4865-1C0E-4E2E-BDAE-AF42EA30DBE8}");
////////////////////////////////////////////////////////////////////////////////////////
// Reflection
static void Reflect(AZ::ReflectContext* context);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
InputChannelNameFilteredByDeviceType();
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
virtual ~InputChannelNameFilteredByDeviceType() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the currently selected input device type.
//! \return Currently selected input device type.
inline const AZStd::string& GetInputDeviceType() const { return m_inputDeviceType; }
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the currently selected input channel name.
//! \return Currently selected input channel name.
inline const AZStd::string& GetInputChannelName() const { return m_inputChannelName; }
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! Called when an input device type is selected.
//! \return The AZ::Edit::PropertyRefreshLevels to apply to the property tree view.
virtual AZ::Crc32 OnInputDeviceTypeSelected();
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the name label override to display.
//! \return Name label override to display.
virtual AZStd::string GetNameLabelOverride() const;
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the valid input device types for this input mapping.
//! \return Valid input device types for this input mapping.
virtual AZStd::vector<AZStd::string> GetValidInputDeviceTypes() const;
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the valid input channel names for this input mapping given the selected device type.
//! \return Valid input channel names for this input mapping given the selected device type.
virtual AZStd::vector<AZStd::string> GetValidInputChannelNamesBySelectedDevice() const;
private:
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
AZStd::string m_inputDeviceType; //!< The currently selected input device type.
AZStd::string m_inputChannelName; //!< The currently selected input channel name.
};
////////////////////////////////////////////////////////////////////////////////////////////
//! Base class for input mapping configuration values that are exposed to the editor.
class ConfigBase
{
public:
////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(ConfigBase, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(ConfigBase, "{72EBBBCC-D57E-4085-AFD9-4910506010B6}");
////////////////////////////////////////////////////////////////////////////////////////
// Reflection
static void Reflect(AZ::ReflectContext* context);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
virtual ~ConfigBase() = default;
////////////////////////////////////////////////////////////////////////////////////////
//! Create an input mapping and add it to the input context.
//! \param[in] inputContext Input context that the input mapping will be added to.
AZStd::shared_ptr<InputMapping> CreateInputMappingAndAddToContext(InputContext& inputContext) const;
////////////////////////////////////////////////////////////////////////////////////////
//! Override to create the relevant input mapping.
//! \param[in] inputContext Input context that owns the input mapping.
virtual AZStd::shared_ptr<InputMapping> CreateInputMapping(const InputContext& inputContext) const = 0;
////////////////////////////////////////////////////////////////////////////////////////////
//! Get the name label override to display.
//! \return Name label override to display.
virtual AZStd::string GetNameLabelOverride() const;
protected:
////////////////////////////////////////////////////////////////////////////////////////
//! The unique input channel name (event) output by the input mapping.
AZStd::string m_outputInputChannelName;
};
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputMapping, AZ::SystemAllocator, 0);
@@ -8,9 +8,72 @@
#include <AzFramework/Input/Mappings/InputMappingAnd.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
void InputMappingAnd::Config::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<InputMappingAnd::Config, InputMapping::ConfigBase>()
->Version(0)
->Field("Source Input Channel Names", &Config::m_sourceInputChannelNames)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<InputMappingAnd::Config>("Input Mapping: And",
"Maps multiple different input sources to a single output using 'AND' logic.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &InputMappingAnd::Config::GetNameLabelOverride)
->DataElement(AZ::Edit::UIHandlers::Default, &Config::m_sourceInputChannelNames, "Source Input Channel Names",
"The source input channel names that will be mapped to the output input channel name.")
;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::shared_ptr<InputMapping> InputMappingAnd::Config::CreateInputMapping(const InputContext& inputContext) const
{
if (m_outputInputChannelName.empty())
{
AZ_Error("InputMappingAnd::Config", false, "Cannot create input mapping with empty name.");
return nullptr;
}
if (InputChannelRequests::FindInputChannel(InputChannelId(m_outputInputChannelName.c_str())))
{
AZ_Error("InputMappingAnd::Config", false,
"Cannot create input mapping '%s' with non-unique name.", m_outputInputChannelName.c_str());
return nullptr;
}
if (m_sourceInputChannelNames.empty())
{
AZ_Error("InputMappingAnd::Config", false,
"Cannot create input mapping '%s' with no source inputs.", m_outputInputChannelName.c_str());
return nullptr;
}
const InputChannelId outputInputChannelId(m_outputInputChannelName.c_str());
AZStd::shared_ptr<InputMappingAnd> inputMapping = AZStd::make_shared<InputMappingAnd>(outputInputChannelId,
inputContext);
for (const InputChannelNameFilteredByDeviceType& sourceInputChannelName : m_sourceInputChannelNames)
{
const InputChannelId sourceInputChannelId(sourceInputChannelName.GetInputChannelName().c_str());
inputMapping->AddSourceInput(sourceInputChannelId);
}
return inputMapping;
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputMappingAnd::InputMappingAnd(const InputChannelId& inputChannelId, const InputContext& inputContext)
: InputMapping(inputChannelId, inputContext)
@@ -19,6 +19,38 @@ namespace AzFramework
class InputMappingAnd : public InputMapping
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! The input mapping configuration values that are exposed to the editor.
class Config : public InputMapping::ConfigBase
{
public:
////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(Config, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(Config, "{54E972F3-0477-4E2E-93F5-4E06ED755DF6}", InputMapping::ConfigBase);
////////////////////////////////////////////////////////////////////////////////////////
// Reflection
static void Reflect(AZ::ReflectContext* context);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~Config() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputMapping::Type::CreateInputMapping
AZStd::shared_ptr<InputMapping> CreateInputMapping(const InputContext& inputContext) const override;
private:
////////////////////////////////////////////////////////////////////////////////////////
//! The source input channel names that will be mapped to the output input channel name.
AZStd::vector<InputChannelNameFilteredByDeviceType> m_sourceInputChannelNames;
};
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputMappingAnd, AZ::SystemAllocator, 0);
@@ -8,9 +8,72 @@
#include <AzFramework/Input/Mappings/InputMappingOr.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
void InputMappingOr::Config::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<InputMappingOr::Config, InputMapping::ConfigBase>()
->Version(0)
->Field("Source Input Channel Names", &Config::m_sourceInputChannelNames)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<InputMappingOr::Config>("Input Mapping: Or",
"Maps multiple different input sources to a single output using 'OR' logic.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &InputMappingOr::Config::GetNameLabelOverride)
->DataElement(AZ::Edit::UIHandlers::Default, &Config::m_sourceInputChannelNames, "Source Input Channel Names",
"The source input channel names that will be mapped to the output input channel name.")
;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
AZStd::shared_ptr<InputMapping> InputMappingOr::Config::CreateInputMapping(const InputContext& inputContext) const
{
if (m_outputInputChannelName.empty())
{
AZ_Error("InputMappingOr::Config", false, "Cannot create input mapping with empty name.");
return nullptr;
}
if (InputChannelRequests::FindInputChannel(InputChannelId(m_outputInputChannelName.c_str())))
{
AZ_Error("InputMappingOr::Config", false,
"Cannot create input mapping '%s' with non-unique name.", m_outputInputChannelName.c_str());
return nullptr;
}
if (m_sourceInputChannelNames.empty())
{
AZ_Error("InputMappingOr::Config", false,
"Cannot create input mapping '%s' with no source inputs.", m_outputInputChannelName.c_str());
return nullptr;
}
const InputChannelId outputInputChannelId(m_outputInputChannelName.c_str());
AZStd::shared_ptr<InputMappingOr> inputMapping = AZStd::make_shared<InputMappingOr>(outputInputChannelId,
inputContext);
for (const InputChannelNameFilteredByDeviceType& sourceInputChannelName : m_sourceInputChannelNames)
{
const InputChannelId sourceInputChannelId(sourceInputChannelName.GetInputChannelName().c_str());
inputMapping->AddSourceInput(sourceInputChannelId);
}
return inputMapping;
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputMappingOr::InputMappingOr(const InputChannelId& inputChannelId, const InputContext& inputContext)
: InputMapping(inputChannelId, inputContext)
@@ -19,6 +19,38 @@ namespace AzFramework
class InputMappingOr : public InputMapping
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! The input mapping configuration values that are exposed to the editor.
class Config : public InputMapping::ConfigBase
{
public:
////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(Config, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(Config, "{428AFDD4-D353-494A-BBAC-37E00F82CFFD}", InputMapping::ConfigBase);
////////////////////////////////////////////////////////////////////////////////////////
// Reflection
static void Reflect(AZ::ReflectContext* context);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
~Config() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputMapping::Type::CreateInputMapping
AZStd::shared_ptr<InputMapping> CreateInputMapping(const InputContext& inputContext) const override;
private:
////////////////////////////////////////////////////////////////////////////////////////
//! The source input channel names that will be mapped to the output input channel name.
AZStd::vector<InputChannelNameFilteredByDeviceType> m_sourceInputChannelNames;
};
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(InputMappingOr, AZ::SystemAllocator, 0);
@@ -36,6 +36,16 @@ namespace AzFramework
return m_end;
}
const AZ::Entity* const* SpawnableEntityContainerView::begin() const
{
return m_begin;
}
const AZ::Entity* const* SpawnableEntityContainerView::end() const
{
return m_end;
}
const AZ::Entity* const* SpawnableEntityContainerView::cbegin()
{
return m_begin;
@@ -46,11 +56,28 @@ namespace AzFramework
return m_end;
}
size_t SpawnableEntityContainerView::size()
AZ::Entity* SpawnableEntityContainerView::operator[](size_t n)
{
AZ_Assert(n < size(), "Index %zu is out of bounds (size: %llu) for Spawnable Entity Container View", n, size());
return *(m_begin + n);
}
const AZ::Entity* SpawnableEntityContainerView::operator[](size_t n) const
{
AZ_Assert(n < size(), "Index %zu is out of bounds (size: %llu) for Spawnable Entity Container View", n, size());
return *(m_begin + n);
}
size_t SpawnableEntityContainerView::size() const
{
return AZStd::distance(m_begin, m_end);
}
bool SpawnableEntityContainerView::empty() const
{
return m_begin == m_end;
}
//
// SpawnableConstEntityContainerView
@@ -78,6 +105,16 @@ namespace AzFramework
return m_end;
}
const AZ::Entity* const* SpawnableConstEntityContainerView::begin() const
{
return m_begin;
}
const AZ::Entity* const* SpawnableConstEntityContainerView::end() const
{
return m_end;
}
const AZ::Entity* const* SpawnableConstEntityContainerView::cbegin()
{
return m_begin;
@@ -88,11 +125,28 @@ namespace AzFramework
return m_end;
}
size_t SpawnableConstEntityContainerView::size()
const AZ::Entity* SpawnableConstEntityContainerView::operator[](size_t n)
{
AZ_Assert(n < size(), "Index %zu is out of bounds (size: %llu) for Spawnable Const Entity Container View", n, size());
return *(m_begin + n);
}
const AZ::Entity* SpawnableConstEntityContainerView::operator[](size_t n) const
{
AZ_Assert(n < size(), "Index %zu is out of bounds (size: %llu) for Spawnable Entity Container View", n, size());
return *(m_begin + n);
}
size_t SpawnableConstEntityContainerView::size() const
{
return AZStd::distance(m_begin, m_end);
}
bool SpawnableConstEntityContainerView::empty() const
{
return m_begin == m_end;
}
//
// SpawnableIndexEntityPair
@@ -36,11 +36,18 @@ namespace AzFramework
SpawnableEntityContainerView(AZ::Entity** begin, size_t length);
SpawnableEntityContainerView(AZ::Entity** begin, AZ::Entity** end);
AZ::Entity** begin();
AZ::Entity** end();
const AZ::Entity* const* cbegin();
const AZ::Entity* const* cend();
size_t size();
[[nodiscard]] AZ::Entity** begin();
[[nodiscard]] AZ::Entity** end();
[[nodiscard]] const AZ::Entity* const* begin() const;
[[nodiscard]] const AZ::Entity* const* end() const;
[[nodiscard]] const AZ::Entity* const* cbegin();
[[nodiscard]] const AZ::Entity* const* cend();
[[nodiscard]] AZ::Entity* operator[](size_t n);
[[nodiscard]] const AZ::Entity* operator[](size_t n) const;
[[nodiscard]] size_t size() const;
[[nodiscard]] bool empty() const;
private:
AZ::Entity** m_begin;
@@ -53,11 +60,18 @@ namespace AzFramework
SpawnableConstEntityContainerView(AZ::Entity** begin, size_t length);
SpawnableConstEntityContainerView(AZ::Entity** begin, AZ::Entity** end);
const AZ::Entity* const* begin();
const AZ::Entity* const* end();
const AZ::Entity* const* cbegin();
const AZ::Entity* const* cend();
size_t size();
[[nodiscard]] const AZ::Entity* const* begin();
[[nodiscard]] const AZ::Entity* const* end();
[[nodiscard]] const AZ::Entity* const* begin() const;
[[nodiscard]] const AZ::Entity* const* end() const;
[[nodiscard]] const AZ::Entity* const* cbegin();
[[nodiscard]] const AZ::Entity* const* cend();
[[nodiscard]] const AZ::Entity* operator[](size_t n);
[[nodiscard]] const AZ::Entity* operator[](size_t n) const;
[[nodiscard]] size_t size() const;
[[nodiscard]] bool empty() const;
private:
AZ::Entity** m_begin;
@@ -343,6 +343,8 @@ set(FILES
Input/Channels/InputChannelQuaternion.h
Input/Contexts/InputContext.cpp
Input/Contexts/InputContext.h
Input/Contexts/InputContextComponent.cpp
Input/Contexts/InputContextComponent.h
Input/Devices/InputDevice.cpp
Input/Devices/InputDevice.h
Input/Devices/InputDeviceId.cpp
@@ -29,7 +29,6 @@ ly_add_target(
AZ::AzCore
PUBLIC
AZ::GridMate
3rdParty::md5
3rdParty::zlib
3rdParty::zstd
3rdParty::lz4
@@ -68,7 +68,7 @@ namespace UnitTest
return false;
}
if (!archive->OpenPack(path, AZ::IO::IArchive::FLAGS_PATH_REAL))
if (!archive->OpenPack(path))
{
return false;
}
@@ -94,7 +94,7 @@ namespace UnitTest
fileIo->Remove(testArchivePath.c_str());
// ------------ BASIC TEST: Create and read Empty Archive ------------
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath.c_str(), nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath.c_str(), {}, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
EXPECT_NE(nullptr, pArchive);
pArchive.reset();
EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
@@ -122,7 +122,7 @@ namespace UnitTest
checkSums[pos] = static_cast<uint8_t>(pos % 256);
}
auto pArchive = archive->OpenArchive(testArchivePath.c_str(), nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
auto pArchive = archive->OpenArchive(testArchivePath.c_str(), {}, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
EXPECT_NE(nullptr, pArchive);
// the strategy here is to find errors related to file sizes, alignment, overwrites
@@ -143,7 +143,7 @@ namespace UnitTest
// --------------------------------------------- read it back and verify
pArchive = archive->OpenArchive(testArchivePath.c_str(), nullptr, openFlags);
pArchive = archive->OpenArchive(testArchivePath.c_str(), {}, openFlags);
EXPECT_NE(nullptr, pArchive);
for (int j = 0; j < iterations; ++j)
@@ -241,7 +241,7 @@ namespace UnitTest
// -------------------------------------------------------------------------------------------
// read it back and verify
pArchive = archive->OpenArchive(testArchivePath.c_str(), nullptr, openFlags);
pArchive = archive->OpenArchive(testArchivePath.c_str(), {}, openFlags);
EXPECT_NE(nullptr, pArchive);
for (int j = 0; j < iterations; ++j)
@@ -298,7 +298,7 @@ namespace UnitTest
}
// first, reset the pack to the original state:
auto pArchive = archive->OpenArchive(testArchivePath.c_str(), nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
auto pArchive = archive->OpenArchive(testArchivePath.c_str(), {}, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
EXPECT_NE(nullptr, pArchive);
for (int j = 0; j < iterations; ++j)
@@ -382,7 +382,7 @@ namespace UnitTest
// -------------------------------------------------------------------------------------------
// read it back and verify
pArchive = archive->OpenArchive(testArchivePath.c_str(), nullptr, openFlags);
pArchive = archive->OpenArchive(testArchivePath.c_str(), {}, openFlags);
EXPECT_NE(nullptr, pArchive);
writeCount = 0;
+44 -222
View File
@@ -30,16 +30,17 @@ namespace UnitTest
: public ScopedAllocatorSetupFixture
{
public:
// Use an Immediately invoked function to initlaize the m_stackRecordLevels value of the AZ::SystemAllocator::Descriptor class
ArchiveTestFixture()
: m_application{ AZStd::make_unique<AzFramework::Application>() }
: ScopedAllocatorSetupFixture(
[]() { AZ::SystemAllocator::Descriptor desc; desc.m_stackRecordLevels = 30; return desc; }()
)
, m_application{ AZStd::make_unique<AzFramework::Application>() }
{
}
void SetUp() override
{
AZ::ComponentApplication::Descriptor descriptor;
descriptor.m_stackRecordLevels = 30;
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
auto projectPathKey =
@@ -47,7 +48,7 @@ namespace UnitTest
registry->Set(projectPathKey, "AutomatedTesting");
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
m_application->Start(descriptor);
m_application->Start({});
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
// in the unit tests.
@@ -68,7 +69,7 @@ namespace UnitTest
return false;
}
return archive->OpenPack(path, AZ::IO::IArchive::FLAGS_PATH_REAL) && archive->ClosePack(path);
return archive->OpenPack(path) && archive->ClosePack(path);
}
template <class Function>
@@ -116,7 +117,7 @@ namespace UnitTest
{
// Canary tests first
AZ::IO::HandleType fileHandle = archive->FOpen(testFilePath, "rb", 0);
AZ::IO::HandleType fileHandle = archive->FOpen(testFilePath, "rb");
ASSERT_NE(AZ::IO::InvalidHandle, fileHandle);
@@ -136,9 +137,9 @@ namespace UnitTest
// open already open file and call FGetCachedFileData
fileSize = 0;
{
AZ::IO::HandleType fileHandle2 = archive->FOpen(testFilePath, "rb", 0);
AZ::IO::HandleType fileHandle2 = archive->FOpen(testFilePath, "rb");
char* pFileBuffer3 = (char*)archive->FGetCachedFileData(fileHandle2, fileSize);
ASSERT_NE(nullptr,pFileBuffer3);
ASSERT_NE(nullptr, pFileBuffer3);
EXPECT_EQ(dataLen, fileSize);
EXPECT_EQ(0, memcmp(pFileBuffer3, testData, dataLen));
archive->FClose(fileHandle2);
@@ -174,7 +175,7 @@ namespace UnitTest
// Multithreaded Test #2 reading from the same file concurrently
auto concurrentArchiveFileReadFunc = [archive, testFilePath, dataLen, testData]()
{
AZ::IO::HandleType threadFileHandle = archive->FOpen(testFilePath, "rb", 0);
AZ::IO::HandleType threadFileHandle = archive->FOpen(testFilePath, "rb");
if (threadFileHandle == AZ::IO::InvalidHandle)
{
@@ -255,7 +256,7 @@ namespace UnitTest
fileIo->CreatePath("@usercache@/levels/test");
// setup test archive and file
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath_withSubfolders.c_str(), nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath_withSubfolders.c_str(), {}, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
EXPECT_NE(nullptr, pArchive);
EXPECT_EQ(0, pArchive->UpdateFile(fileInArchiveFile, dataString.data(), dataString.size(), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTEST));
pArchive.reset();
@@ -290,7 +291,7 @@ namespace UnitTest
archive->ClosePack(filePath.c_str());
fileIo->Remove(filePath.c_str());
auto pArchive = archive->OpenArchive(filePath.c_str(), nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
auto pArchive = archive->OpenArchive(filePath.c_str(), {}, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
EXPECT_NE(nullptr, pArchive);
pArchive.reset();
archive->ClosePack(filePath.c_str());
@@ -305,7 +306,7 @@ namespace UnitTest
// open and fetch the opened pak file using a *.pak
AZStd::vector<AZ::IO::FixedMaxPathString> fullPaths;
archive->OpenPacks("@usercache@/*.pak", AZ::IO::IArchive::EPathResolutionRules::FLAGS_PATH_REAL, &fullPaths);
archive->OpenPacks("@usercache@/*.pak", &fullPaths);
EXPECT_TRUE(AZStd::any_of(fullPaths.cbegin(), fullPaths.cend(), [](auto& path) { return path.ends_with("one.pak"); }));
EXPECT_TRUE(AZStd::any_of(fullPaths.cbegin(), fullPaths.cend(), [](auto& path) { return path.ends_with("two.pak"); }));
}
@@ -477,7 +478,7 @@ namespace UnitTest
bool found_mylevel_file{};
bool found_mylevel_folder{};
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath_withMountPoint, nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath_withMountPoint, {}, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
EXPECT_NE(nullptr, pArchive);
EXPECT_EQ(0, pArchive->UpdateFile("levelinfo.xml", dataString.data(), dataString.size(), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTEST));
pArchive.reset();
@@ -628,7 +629,7 @@ namespace UnitTest
normalFileHandle = InvalidHandle;
EXPECT_TRUE(cpfio.Exists("@log@/unittesttemp/realfileforunittest.xml"));
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(genericArchiveFileName, nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(genericArchiveFileName, {}, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
EXPECT_NE(nullptr, pArchive);
EXPECT_EQ(0, pArchive->UpdateFile("testfile.xml", dataString, aznumeric_cast<uint32_t>(dataLen), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTEST));
pArchive.reset();
@@ -712,13 +713,16 @@ namespace UnitTest
EXPECT_TRUE(cpfio.Exists("@log@/unittesttemp/copiedfile2.xml"));
// find files test.
AZ::IO::FixedMaxPath resolvedTestFilePath;
EXPECT_TRUE(cpfio.ResolvePath(resolvedTestFilePath, AZ::IO::PathView("@assets@/testfile.xml")));
bool foundIt = false;
// note that this file exists only in the archive.
cpfio.FindFiles("@assets@", "*.xml", [&foundIt](const char* foundName)
cpfio.FindFiles("@assets@", "*.xml", [&foundIt, &cpfio, &resolvedTestFilePath](const char* foundName)
{
AZ::IO::FixedMaxPath resolvedFoundPath;
EXPECT_TRUE(cpfio.ResolvePath(resolvedFoundPath, AZ::IO::PathView(foundName)));
// according to the contract stated in the FileIO.h file, we expect full paths. (Aliases are full paths)
if (azstricmp(foundName, "@assets@/testfile.xml") == 0)
if (resolvedTestFilePath == resolvedFoundPath)
{
foundIt = true;
return false;
@@ -769,7 +773,7 @@ namespace UnitTest
fileIo->Remove(testArchivePath);
// ------------ BASIC TEST: Create and read Empty Archive ------------
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath, nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath, {}, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
EXPECT_NE(nullptr, pArchive);
EXPECT_EQ(0, pArchive->UpdateFile("foundit.dat", const_cast<char*>("test"), 4, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BEST));
@@ -803,40 +807,49 @@ namespace UnitTest
EXPECT_TRUE(archive->ClosePack(realNameBuf));
}
TEST_F(ArchiveTestFixture, IResourceList_Add_EmptyFileName_DoesNotCrash)
TEST_F(ArchiveTestFixture, IResourceList_Add_EmptyFileName_DoesNotInsert)
{
AZ::IO::IResourceList* reslist = AZ::Interface<AZ::IO::IArchive>::Get()->GetResourceList(AZ::IO::IArchive::RFOM_EngineStartup);
ASSERT_NE(nullptr, reslist);
reslist->Clear();
reslist->Add("");
EXPECT_STREQ(reslist->GetFirst(), "");
reslist->Clear();
EXPECT_EQ(nullptr, reslist->GetFirst());
}
TEST_F(ArchiveTestFixture, IResourceList_Add_RegularFileName_NormalizesAppropriately)
TEST_F(ArchiveTestFixture, IResourceList_Add_RegularFileName_ResolvesAppropriately)
{
AZ::IO::IResourceList* reslist = AZ::Interface<AZ::IO::IArchive>::Get()->GetResourceList(AZ::IO::IArchive::RFOM_EngineStartup);
ASSERT_NE(nullptr, reslist);
AZ::IO::FileIOBase* ioBase = AZ::IO::FileIOBase::GetInstance();
ASSERT_NE(nullptr, ioBase);
AZ::IO::FixedMaxPath resolvedTestPath;
EXPECT_TRUE(ioBase->ResolvePath(resolvedTestPath, "blah/blah/abcde"));
reslist->Clear();
reslist->Add("blah\\blah/AbCDE");
// it normalizes the string, so the slashes flip and everything is lowercased.
EXPECT_STREQ(reslist->GetFirst(), "blah/blah/abcde");
AZ::IO::FixedMaxPath resolvedAddedPath;
EXPECT_TRUE(ioBase->ResolvePath(resolvedAddedPath, reslist->GetFirst()));
EXPECT_EQ(resolvedTestPath, resolvedAddedPath);
reslist->Clear();
}
TEST_F(ArchiveTestFixture, IResourceList_Add_ReallyShortFileName_NormalizesAppropriately)
TEST_F(ArchiveTestFixture, IResourceList_Add_ReallyShortFileName_ResolvesAppropriately)
{
AZ::IO::IResourceList* reslist = AZ::Interface<AZ::IO::IArchive>::Get()->GetResourceList(AZ::IO::IArchive::RFOM_EngineStartup);
ASSERT_NE(nullptr, reslist);
AZ::IO::FileIOBase* ioBase = AZ::IO::FileIOBase::GetInstance();
ASSERT_NE(nullptr, ioBase);
AZ::IO::FixedMaxPath resolvedTestPath;
EXPECT_TRUE(ioBase->ResolvePath(resolvedTestPath, "a"));
reslist->Clear();
reslist->Add("A");
// it normalizes the string, so the slashes flip and everything is lowercased.
EXPECT_STREQ(reslist->GetFirst(), "a");
AZ::IO::FixedMaxPath resolvedAddedPath;
EXPECT_TRUE(ioBase->ResolvePath(resolvedAddedPath, reslist->GetFirst()));
EXPECT_EQ(resolvedTestPath, resolvedAddedPath);
reslist->Clear();
}
@@ -848,7 +861,7 @@ namespace UnitTest
AZ::IO::FileIOBase* ioBase = AZ::IO::FileIOBase::GetInstance();
ASSERT_NE(nullptr, ioBase);
const char *assetsPath = ioBase->GetAlias("@assets@");
const char* assetsPath = ioBase->GetAlias("@assets@");
ASSERT_NE(nullptr, assetsPath);
auto stringToAdd = AZ::IO::Path(assetsPath) / "textures" / "test.dds";
@@ -864,195 +877,4 @@ namespace UnitTest
EXPECT_EQ(resolvedAddedPath, resolvedResourcePath);
reslist->Clear();
}
class ArchiveUnitTestsWithAllocators
: public ScopedAllocatorSetupFixture
{
protected:
void SetUp() override
{
m_localFileIO = aznew AZ::IO::LocalFileIO();
AZ::IO::FileIOBase::SetDirectInstance(m_localFileIO);
m_localFileIO->SetAlias(m_firstAlias.c_str(), m_firstAliasPath.c_str());
m_localFileIO->SetAlias(m_secondAlias.c_str(), m_secondAliasPath.c_str());
}
void TearDown() override
{
AZ::IO::FileIOBase::SetDirectInstance(nullptr);
delete m_localFileIO;
m_localFileIO = nullptr;
}
AZ::IO::FileIOBase* m_localFileIO = nullptr;
AZStd::string m_firstAlias = "@devassets@";
AZStd::string m_firstAliasPath = "devassets_absolutepath";
AZStd::string m_secondAlias = "@assets@";
AZStd::string m_secondAliasPath = "assets_absolutepath";
};
// ConvertAbsolutePathToAliasedPath tests are built to verify existing behavior doesn't change.
// It's a legacy function and the actual intended behavior is unknown, so these are black box unit tests.
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_NullString_ReturnsSuccess)
{
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(nullptr);
EXPECT_TRUE(conversionResult);
EXPECT_TRUE(conversionResult->empty());
}
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_NoAliasInSource_ReturnsSource)
{
AZStd::string sourceString("NoAlias");
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(sourceString.c_str());
EXPECT_TRUE(conversionResult);
// ConvertAbsolutePathToAliasedPath returns sourceString if there is no alias in the source.
EXPECT_STREQ(sourceString.c_str(), conversionResult->c_str());
}
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_NullAliasToLookFor_ReturnsSource)
{
AZStd::string sourceString("NoAlias");
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(sourceString.c_str(), nullptr);
EXPECT_TRUE(conversionResult);
EXPECT_STREQ(sourceString.c_str(), conversionResult->c_str());
}
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_NullAliasToReplaceWith_ReturnsSource)
{
AZStd::string sourceString("NoAlias");
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(sourceString.c_str(), "@SomeAlias", nullptr);
EXPECT_TRUE(conversionResult);
EXPECT_STREQ(sourceString.c_str(), conversionResult->c_str());
}
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_NullAliases_ReturnsSource)
{
AZStd::string sourceString("NoAlias");
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(sourceString.c_str(), nullptr, nullptr);
EXPECT_TRUE(conversionResult);
EXPECT_STREQ(sourceString.c_str(), conversionResult->c_str());
}
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_AbsPathInSource_ReturnsReplacedAlias)
{
// ConvertAbsolutePathToAliasedPath only replaces data if GetDirectInstance is valid.
EXPECT_TRUE(AZ::IO::FileIOBase::GetDirectInstance() != nullptr);
const char* fullPath = AZ::IO::FileIOBase::GetDirectInstance()->GetAlias(m_firstAlias.c_str());
AZStd::string sourceString = AZStd::string::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "SomeStringWithAlias", fullPath);
AZStd::string expectedResult = AZStd::string::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "somestringwithalias", m_secondAlias.c_str());
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(
sourceString.c_str(),
m_firstAlias.c_str(), // find any instance of FirstAlias in sourceString
m_secondAlias.c_str()); // replace it with SecondAlias
EXPECT_TRUE(conversionResult);
EXPECT_STREQ(conversionResult->c_str(), expectedResult.c_str());
}
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_AliasInSource_ReturnsReplacedAlias)
{
// ConvertAbsolutePathToAliasedPath only replaces data if GetDirectInstance is valid.
EXPECT_TRUE(AZ::IO::FileIOBase::GetDirectInstance() != nullptr);
AZStd::string sourceString = AZStd::string::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "SomeStringWithAlias", m_firstAlias.c_str());
AZStd::string expectedResult = AZStd::string::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "somestringwithalias", m_secondAlias.c_str());
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(
sourceString.c_str(),
m_firstAlias.c_str(), // find any instance of FirstAlias in sourceString
m_secondAlias.c_str()); // replace it with SecondAlias
EXPECT_TRUE(conversionResult);
EXPECT_STREQ(conversionResult->c_str(), expectedResult.c_str());
}
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_AbsPathInSource_DOSSlashInSource_ReturnsReplacedAlias)
{
// ConvertAbsolutePathToAliasedPath only replaces data if GetDirectInstance is valid.
EXPECT_TRUE(AZ::IO::FileIOBase::GetDirectInstance() != nullptr);
const char* fullPath = AZ::IO::FileIOBase::GetDirectInstance()->GetAlias(m_firstAlias.c_str());
AZStd::string sourceString = AZStd::string::format("%s" AZ_WRONG_DATABASE_SEPARATOR_STRING "SomeStringWithAlias", fullPath);
AZStd::string expectedResult = AZStd::string::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "somestringwithalias", m_secondAlias.c_str());
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(
sourceString.c_str(),
m_firstAlias.c_str(), // find any instance of FirstAlias in sourceString
m_secondAlias.c_str()); // replace it with SecondAlias
EXPECT_TRUE(conversionResult);
EXPECT_STREQ(conversionResult->c_str(), expectedResult.c_str());
}
#endif // AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_AbsPathInSource_UNIXSlashInSource_ReturnsReplacedAlias)
{
// ConvertAbsolutePathToAliasedPath only replaces data if GetDirectInstance is valid.
EXPECT_TRUE(AZ::IO::FileIOBase::GetDirectInstance() != nullptr);
const char* fullPath = AZ::IO::FileIOBase::GetDirectInstance()->GetAlias(m_firstAlias.c_str());
AZStd::string sourceString = AZStd::string::format("%s" AZ_CORRECT_DATABASE_SEPARATOR_STRING "SomeStringWithAlias", fullPath);
AZStd::string expectedResult = AZStd::string::format("%s" AZ_CORRECT_DATABASE_SEPARATOR_STRING "somestringwithalias", m_secondAlias.c_str());
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(
sourceString.c_str(),
m_firstAlias.c_str(), // find any instance of FirstAlias in sourceString
m_secondAlias.c_str()); // replace it with SecondAlias
EXPECT_TRUE(conversionResult);
EXPECT_STREQ(conversionResult->c_str(), expectedResult.c_str());
}
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_AliasInSource_DOSSlashInSource_ReturnsReplacedAlias)
{
// ConvertAbsolutePathToAliasedPath only replaces data if GetDirectInstance is valid.
EXPECT_TRUE(AZ::IO::FileIOBase::GetDirectInstance() != nullptr);
AZStd::string sourceString = AZStd::string::format("%s" AZ_WRONG_DATABASE_SEPARATOR_STRING "SomeStringWithAlias", m_firstAlias.c_str());
AZStd::string expectedResult = AZStd::string::format("%s" AZ_WRONG_DATABASE_SEPARATOR_STRING "somestringwithalias", m_secondAlias.c_str());
// sourceString is now (firstAlias)SomeStringWithAlias
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(
sourceString.c_str(),
m_firstAlias.c_str(), // find any instance of FirstAlias in sourceString
m_secondAlias.c_str()); // replace it with SecondAlias
EXPECT_TRUE(conversionResult);
EXPECT_STREQ(conversionResult->c_str(), expectedResult.c_str());
}
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_AliasInSource_UNIXSlashInSource_ReturnsReplacedAlias)
{
// ConvertAbsolutePathToAliasedPath only replaces data if GetDirectInstance is valid.
EXPECT_TRUE(AZ::IO::FileIOBase::GetDirectInstance() != nullptr);
AZStd::string sourceString = AZStd::string::format("%s" AZ_CORRECT_DATABASE_SEPARATOR_STRING "SomeStringWithAlias", m_firstAlias.c_str());
AZStd::string expectedResult = AZStd::string::format("%s" AZ_CORRECT_DATABASE_SEPARATOR_STRING "somestringwithalias", m_secondAlias.c_str());
// sourceString is now (firstAlias)SomeStringWithAlias
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(
sourceString.c_str(),
m_firstAlias.c_str(), // find any instance of FirstAlias in sourceString
m_secondAlias.c_str()); // replace it with SecondAlias
EXPECT_TRUE(conversionResult);
EXPECT_STREQ(conversionResult->c_str(), expectedResult.c_str());
}
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_SourceLongerThanMaxPath_ReturnsFailure)
{
const int longPathArraySize = AZ::IO::MaxPathLength + 2;
char longPath[longPathArraySize];
memset(longPath, 'a', sizeof(char) * longPathArraySize);
longPath[longPathArraySize - 1] = '\0';
AZ_TEST_START_TRACE_SUPPRESSION;
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(longPath);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
EXPECT_FALSE(conversionResult);
}
class ArchivePathCompareTestFixture
: public ScopedAllocatorSetupFixture
, public ::testing::WithParamInterface<AZStd::tuple<AZStd::string_view, AZStd::string_view>>
{
};
}
@@ -0,0 +1,122 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/std/containers/array.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
#include <AzTest/AzTest.h>
namespace UnitTest
{
//
// SpawnableEntityContainerView
//
class SpawnableEntityContainerViewTest : public ::testing::Test
{
protected:
AZStd::array<AZ::Entity*, 4> m_values{ reinterpret_cast<AZ::Entity*>(1), reinterpret_cast<AZ::Entity*>(2),
reinterpret_cast<AZ::Entity*>(3), reinterpret_cast<AZ::Entity*>(4) };
AzFramework::SpawnableEntityContainerView m_view{ m_values.begin(), m_values.end() };
};
TEST_F(SpawnableEntityContainerViewTest, begin_Get_MatchesBeginOfArray)
{
EXPECT_EQ(m_view.begin(), m_values.begin());
}
TEST_F(SpawnableEntityContainerViewTest, end_Get_MatchesEndOfArray)
{
EXPECT_EQ(m_view.end(), m_values.end());
}
TEST_F(SpawnableEntityContainerViewTest, cbegin_Get_MatchesBeginOfArray)
{
EXPECT_EQ(m_view.cbegin(), m_values.cbegin());
}
TEST_F(SpawnableEntityContainerViewTest, cend_Get_MatchesEndOfArray)
{
EXPECT_EQ(m_view.cend(), m_values.cend());
}
TEST_F(SpawnableEntityContainerViewTest, IndexOperator_Get_MatchesThirdElement)
{
EXPECT_EQ(m_view[2], m_values[2]);
}
TEST_F(SpawnableEntityContainerViewTest, Size_Get_MatchesSizeOfArray)
{
EXPECT_EQ(m_view.size(), m_values.size());
}
TEST_F(SpawnableEntityContainerViewTest, empty_GetFromFilledArray_ReturnsFalse)
{
EXPECT_FALSE(m_view.empty());
}
TEST_F(SpawnableEntityContainerViewTest, empty_GetFromEmtpyView_ReturnsTrue)
{
AzFramework::SpawnableEntityContainerView view{ nullptr, nullptr };
EXPECT_TRUE(view.empty());
}
//
// SpawnableConstEntityContainerView
//
class SpawnableConstEntityContainerViewTest : public ::testing::Test
{
protected:
AZStd::array<AZ::Entity*, 4> m_values{ reinterpret_cast<AZ::Entity*>(1), reinterpret_cast<AZ::Entity*>(2),
reinterpret_cast<AZ::Entity*>(3), reinterpret_cast<AZ::Entity*>(4) };
AzFramework::SpawnableConstEntityContainerView m_view{ m_values.begin(), m_values.end() };
};
TEST_F(SpawnableConstEntityContainerViewTest, begin_Get_MatchesBeginOfArray)
{
EXPECT_EQ(m_view.begin(), m_values.begin());
}
TEST_F(SpawnableConstEntityContainerViewTest, end_Get_MatchesEndOfArray)
{
EXPECT_EQ(m_view.end(), m_values.end());
}
TEST_F(SpawnableConstEntityContainerViewTest, cbegin_Get_MatchesBeginOfArray)
{
EXPECT_EQ(m_view.cbegin(), m_values.cbegin());
}
TEST_F(SpawnableConstEntityContainerViewTest, cend_Get_MatchesEndOfArray)
{
EXPECT_EQ(m_view.cend(), m_values.cend());
}
TEST_F(SpawnableConstEntityContainerViewTest, IndexOperator_Get_MatchesThirdElement)
{
EXPECT_EQ(m_view[2], m_values[2]);
}
TEST_F(SpawnableConstEntityContainerViewTest, size_Get_MatchesSizeOfArray)
{
EXPECT_EQ(m_view.size(), m_values.size());
}
TEST_F(SpawnableConstEntityContainerViewTest, empty_GetFromFilledArray_ReturnsFalse)
{
EXPECT_FALSE(m_view.empty());
}
TEST_F(SpawnableConstEntityContainerViewTest, empty_GetFromEmtpyView_ReturnsTrue)
{
AzFramework::SpawnableConstEntityContainerView view{ nullptr, nullptr };
EXPECT_TRUE(view.empty());
}
} // namespace UnitTest
@@ -8,6 +8,7 @@
set(FILES
../../AzCore/Tests/Main.cpp
Spawnable/SpawnableEntitiesInterfaceTests.cpp
Spawnable/SpawnableEntitiesManagerTests.cpp
ArchiveCompressionTests.cpp
ArchiveTests.cpp
@@ -9,12 +9,10 @@
#include "GameApplication.h"
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/std/string/conversions.h>
#include <GridMate/Drillers/CarrierDriller.h>
#include <GridMate/Drillers/ReplicaDriller.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Archive/Archive.h>
#include <AzFramework/TargetManagement/TargetManagementComponent.h>
#include <AzFramework/Metrics/MetricsPlainTextNameRegistration.h>
#include <AzGameFramework/AzGameFrameworkModule.h>
namespace AzGameFramework
@@ -26,6 +24,28 @@ namespace AzGameFramework
GameApplication::GameApplication(int argc, char** argv)
: Application(&argc, &argv)
{
// In the Launcher Applications the Settings Registry
// can read from the FileIOBase instance if available
m_settingsRegistry->SetUseFileIO(true);
// Attempt to mount the engine pak from the Executable Directory
// at the Assets alias, otherwise to attempting to mount the engine pak
// from the Cache folder
AZ::IO::FixedMaxPath enginePakPath = AZ::Utils::GetExecutableDirectory();
enginePakPath /= "Engine.pak";
if (m_archiveFileIO->Exists(enginePakPath.c_str()))
{
m_archive->OpenPack("@assets@", enginePakPath.Native());
}
else if (enginePakPath.clear(); m_settingsRegistry->Get(enginePakPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
{
// fall back to checking if there is an Engine.pak in the Asset Cache
enginePakPath /= "Engine.pak";
if (m_archiveFileIO->Exists(enginePakPath.c_str()))
{
m_archive->OpenPack("@assets@", enginePakPath.Native());
}
}
}
GameApplication::~GameApplication()
@@ -50,8 +70,8 @@ namespace AzGameFramework
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
#endif
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_TargetBuildDependencyRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
@@ -15,19 +15,19 @@
namespace AzManipulatorTestFramework
{
//! Base class for derived immediate and retained action dispatchers.
//! Base class for derived immediate action dispatchers.
template<typename DerivedDispatcherT>
class ActionDispatcher
{
public:
virtual ~ActionDispatcher() = default;
//! Enable grid snapping.
DerivedDispatcherT* EnableSnapToGrid();
//! Disable grid snapping.
DerivedDispatcherT* DisableSnapToGrid();
//! Enable/disable grid snapping.
DerivedDispatcherT* SetSnapToGrid(bool enabled);
//! Set the grid size.
DerivedDispatcherT* GridSize(float size);
//! Enable/disable sticky select.
DerivedDispatcherT* SetStickySelect(bool enabled);
//! Enable/disable action logging.
DerivedDispatcherT* LogActions(bool logging);
//! Output a trace debug message.
@@ -66,9 +66,9 @@ namespace AzManipulatorTestFramework
DerivedDispatcherT* EnterComponentMode();
protected:
// Actions to be implemented by derived immediate and retained action dispatchers.
virtual void EnableSnapToGridImpl() = 0;
virtual void DisableSnapToGridImpl() = 0;
// Actions to be implemented by derived immediate action dispatcher.
virtual void SetSnapToGridImpl(bool enabled) = 0;
virtual void SetStickySelectImpl(bool enabled) = 0;
virtual void GridSizeImpl(float size) = 0;
virtual void CameraStateImpl(const AzFramework::CameraState& cameraState) = 0;
virtual void MouseLButtonDownImpl() = 0;
@@ -127,18 +127,18 @@ namespace AzManipulatorTestFramework
}
template<typename DerivedDispatcherT>
DerivedDispatcherT* ActionDispatcher<DerivedDispatcherT>::EnableSnapToGrid()
DerivedDispatcherT* ActionDispatcher<DerivedDispatcherT>::SetSnapToGrid(const bool enabled)
{
Log("Enabling SnapToGrid");
EnableSnapToGridImpl();
Log("SnapToGrid %s", enabled ? "on" : "off");
SetSnapToGridImpl(enabled);
return static_cast<DerivedDispatcherT*>(this);
}
template<typename DerivedDispatcherT>
DerivedDispatcherT* ActionDispatcher<DerivedDispatcherT>::DisableSnapToGrid()
DerivedDispatcherT* ActionDispatcher<DerivedDispatcherT>::SetStickySelect(bool enabled)
{
Log("Disabling SnapToGrid");
DisableSnapToGridImpl();
Log("StickySelect %s", enabled ? "on" : "off");
SetStickySelectImpl(enabled);
return static_cast<DerivedDispatcherT*>(this);
}
@@ -16,7 +16,7 @@ namespace AzFramework
{
class DebugDisplayRequests;
struct CameraState;
}
} // namespace AzFramework
namespace AzManipulatorTestFramework
{
@@ -31,14 +31,10 @@ namespace AzManipulatorTestFramework
virtual void SetCameraState(const AzFramework::CameraState& cameraState) = 0;
//! Retrieve the debug display.
virtual AzFramework::DebugDisplayRequests& GetDebugDisplay() = 0;
//! Enable grid snapping.
virtual void EnableGridSnaping() = 0;
//! Disable grid snapping.
virtual void DisableGridSnaping() = 0;
//! Enable grid snapping.
virtual void EnableAngularSnaping() = 0;
//! Disable grid snapping.
virtual void DisableAngularSnaping() = 0;
//! Set if grid snapping is enabled or not.
virtual void SetGridSnapping(bool enabled) = 0;
//! Set if angular snapping is enabled or not.
virtual void SetAngularSnapping(bool enabled) = 0;
//! Set the grid size.
virtual void SetGridSize(float size) = 0;
//! Set the angular step.
@@ -48,6 +44,8 @@ namespace AzManipulatorTestFramework
//! Updates the visibility state.
//! Updates which entities are currently visible given the current camera state.
virtual void UpdateVisibility() = 0;
//! Set if sticky select is enabled or not.
virtual void SetStickySelect(bool enabled) = 0;
};
//! This interface is used to simulate the manipulator manager while the manipulators are under test.
@@ -82,15 +80,15 @@ namespace AzManipulatorTestFramework
//! Return the representation of the viewport interaction model.
ViewportInteractionInterface& GetViewportInteraction()
{
return const_cast<
ViewportInteractionInterface&>(const_cast<const ManipulatorViewportInteraction*>(this)->GetViewportInteraction());
return const_cast<ViewportInteractionInterface&>(
const_cast<const ManipulatorViewportInteraction*>(this)->GetViewportInteraction());
}
//! Return the const representation of the manipulator manager.
ManipulatorManagerInterface& GetManipulatorManager()
{
return const_cast<
ManipulatorManagerInterface&>(const_cast<const ManipulatorViewportInteraction*>(this)->GetManipulatorManager());
return const_cast<ManipulatorManagerInterface&>(
const_cast<const ManipulatorViewportInteraction*>(this)->GetManipulatorManager());
}
};
} // namespace AzManipulatorTestFramework
@@ -52,8 +52,8 @@ namespace AzManipulatorTestFramework
protected:
// ActionDispatcher ...
void EnableSnapToGridImpl() override;
void DisableSnapToGridImpl() override;
void SetSnapToGridImpl(bool enabled) override;
void SetStickySelectImpl(bool enabled) override;
void GridSizeImpl(float size) override;
void CameraStateImpl(const AzFramework::CameraState& cameraState) override;
void MouseLButtonDownImpl() override;
@@ -29,14 +29,13 @@ namespace AzManipulatorTestFramework
// ViewportInteractionInterface overrides ...
void SetCameraState(const AzFramework::CameraState& cameraState) override;
AzFramework::DebugDisplayRequests& GetDebugDisplay() override;
void EnableGridSnaping() override;
void DisableGridSnaping() override;
void EnableAngularSnaping() override;
void DisableAngularSnaping() override;
void SetGridSnapping(bool enabled) override;
void SetAngularSnapping(bool enabled) override;
void SetGridSize(float size) override;
void SetAngularStep(float step) override;
int GetViewportId() const override;
void UpdateVisibility() override;
void SetStickySelect(bool enabled) override;
// ViewportInteractionRequestBus overrides ...
AzFramework::CameraState GetCameraState() override;
@@ -54,6 +53,7 @@ namespace AzManipulatorTestFramework
float AngleStep() const override;
float ManipulatorLineBoundWidth() const override;
float ManipulatorCircleBoundWidth() const override;
bool StickySelectEnabled() const override;
// EditorEntityViewportInteractionRequestBus overrides ...
void FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntities) override;
@@ -65,6 +65,7 @@ namespace AzManipulatorTestFramework
AzFramework::CameraState m_cameraState;
bool m_gridSnapping = false;
bool m_angularSnapping = false;
bool m_stickySelect = true;
float m_gridSize = 1.0f;
float m_angularStep = 0.0f;
};
@@ -49,17 +49,17 @@ namespace AzManipulatorTestFramework
m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
}
void ImmediateModeActionDispatcher::EnableSnapToGridImpl()
void ImmediateModeActionDispatcher::SetSnapToGridImpl(const bool enabled)
{
m_viewportManipulatorInteraction.GetViewportInteraction().EnableGridSnaping();
m_viewportManipulatorInteraction.GetViewportInteraction().SetGridSnapping(enabled);
}
void ImmediateModeActionDispatcher::DisableSnapToGridImpl()
void ImmediateModeActionDispatcher::SetStickySelectImpl(const bool enabled)
{
m_viewportManipulatorInteraction.GetViewportInteraction().DisableGridSnaping();
m_viewportManipulatorInteraction.GetViewportInteraction().SetStickySelect(enabled);
}
void ImmediateModeActionDispatcher::GridSizeImpl(float size)
void ImmediateModeActionDispatcher::GridSizeImpl(const float size)
{
m_viewportManipulatorInteraction.GetViewportInteraction().SetGridSize(size);
}
@@ -6,16 +6,15 @@
*
*/
#include <AzManipulatorTestFramework/ViewportInteraction.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzManipulatorTestFramework/ViewportInteraction.h>
#include <AzToolsFramework/Manipulators/ManipulatorBus.h>
namespace AzManipulatorTestFramework
{
// Null debug display for dummy draw calls
class NullDebugDisplayRequests
: public AzFramework::DebugDisplayRequests
class NullDebugDisplayRequests : public AzFramework::DebugDisplayRequests
{
public:
virtual ~NullDebugDisplayRequests() = default;
@@ -76,6 +75,11 @@ namespace AzManipulatorTestFramework
return 0.1f;
}
bool ViewportInteraction::StickySelectEnabled() const
{
return m_stickySelect;
}
void ViewportInteraction::FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntitiesOut)
{
visibleEntitiesOut.assign(m_entityVisibilityQuery.Begin(), m_entityVisibilityQuery.End());
@@ -101,24 +105,19 @@ namespace AzManipulatorTestFramework
return *m_nullDebugDisplayRequests;
}
void ViewportInteraction::EnableGridSnaping()
void ViewportInteraction::SetGridSnapping(const bool enabled)
{
m_gridSnapping = true;
m_gridSnapping = enabled;
}
void ViewportInteraction::DisableGridSnaping()
void ViewportInteraction::SetAngularSnapping(const bool enabled)
{
m_gridSnapping = false;
m_angularSnapping = enabled;
}
void ViewportInteraction::EnableAngularSnaping()
void ViewportInteraction::SetStickySelect(const bool enabled)
{
m_angularSnapping = true;
}
void ViewportInteraction::DisableAngularSnaping()
{
m_angularSnapping = false;
m_stickySelect = enabled;
}
void ViewportInteraction::SetGridSize(float size)
@@ -152,4 +151,4 @@ namespace AzManipulatorTestFramework
{
return 1.0f;
}
}// namespace AzManipulatorTestFramework
} // namespace AzManipulatorTestFramework
@@ -76,7 +76,7 @@ namespace UnitTest
linearManipulator->SetLocalPosition(action.LocalPosition());
});
m_actionDispatcher->EnableSnapToGrid()
m_actionDispatcher->SetSnapToGrid(true)
->GridSize(5.0f)
->CameraState(m_cameraState)
->MousePosition(initialPositionScreen)
@@ -114,7 +114,7 @@ namespace UnitTest
manipulator->SetLocalPosition(action.LocalPosition());
});
actionDispatcher->EnableSnapToGrid()
actionDispatcher->SetSnapToGrid(true)
->GridSize(1.0f)
->CameraState(cameraState)
->MousePosition(initialPositionScreen)
@@ -48,7 +48,7 @@ namespace UnitTest
{
bool snapping = false;
m_viewportInteraction->EnableGridSnaping();
m_viewportInteraction->SetGridSnapping(true);
AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::EventResult(
snapping, m_viewportInteraction->GetViewportId(),
&AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Events::GridSnappingEnabled);
@@ -60,7 +60,7 @@ namespace UnitTest
{
bool snapping = true;
m_viewportInteraction->DisableGridSnaping();
m_viewportInteraction->SetGridSnapping(false);
AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::EventResult(
snapping, m_viewportInteraction->GetViewportId(),
&AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Events::GridSnappingEnabled);
@@ -75,7 +75,7 @@ namespace UnitTest
m_viewportInteraction->SetGridSize(expectedGridSize);
m_viewportInteraction->DisableGridSnaping();
m_viewportInteraction->SetGridSnapping(false);
AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::EventResult(
gridSize, m_viewportInteraction->GetViewportId(),
&AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Events::GridSize);
@@ -87,7 +87,7 @@ namespace UnitTest
{
bool snapping = false;
m_viewportInteraction->EnableAngularSnaping();
m_viewportInteraction->SetAngularSnapping(true);
AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::EventResult(
snapping, m_viewportInteraction->GetViewportId(),
&AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Events::AngleSnappingEnabled);
@@ -99,7 +99,7 @@ namespace UnitTest
{
bool snapping = true;
m_viewportInteraction->DisableAngularSnaping();
m_viewportInteraction->SetAngularSnapping(false);
AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::EventResult(
snapping, m_viewportInteraction->GetViewportId(),
&AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Events::AngleSnappingEnabled);

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