Merge branch 'development' into memory/benchmarks

Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>
monroegm-disable-blank-issue-2
Esteban Papp 4 years ago
commit da5ec1f478

@ -0,0 +1,131 @@
{
"ContainerEntity": {
"Id": "ContainerEntity",
"Name": "PinkFlower",
"Components": {
"Component_[10444337162843472597]": {
"$type": "EditorLockComponent",
"Id": 10444337162843472597
},
"Component_[14431042590323756177]": {
"$type": "EditorEntitySortComponent",
"Id": 14431042590323756177,
"ChildEntityOrderEntryArray": [
{
"EntityId": "Entity_[491002114939]"
}
]
},
"Component_[14577735453176806353]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 14577735453176806353
},
"Component_[15674021346798629563]": {
"$type": "EditorInspectorComponent",
"Id": 15674021346798629563
},
"Component_[16784074985702513600]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 16784074985702513600,
"Parent Entity": ""
},
"Component_[3351614541100572773]": {
"$type": "EditorOnlyEntityComponent",
"Id": 3351614541100572773
},
"Component_[3600658560328167663]": {
"$type": "EditorPrefabComponent",
"Id": 3600658560328167663
},
"Component_[6155673728651558934]": {
"$type": "EditorEntityIconComponent",
"Id": 6155673728651558934
},
"Component_[8458684662170321289]": {
"$type": "EditorVisibilityComponent",
"Id": 8458684662170321289
},
"Component_[8705192117416252351]": {
"$type": "SelectionComponent",
"Id": 8705192117416252351
},
"Component_[8801280051488695852]": {
"$type": "EditorPendingCompositionComponent",
"Id": 8801280051488695852
}
}
},
"Entities": {
"Entity_[491002114939]": {
"Id": "Entity_[491002114939]",
"Name": "PinkFlower",
"Components": {
"Component_[11083205340162142682]": {
"$type": "EditorLockComponent",
"Id": 11083205340162142682
},
"Component_[11327363779873517]": {
"$type": "EditorOnlyEntityComponent",
"Id": 11327363779873517
},
"Component_[12717389211269537921]": {
"$type": "EditorEntitySortComponent",
"Id": 12717389211269537921
},
"Component_[12826285685970138542]": {
"$type": "AZ::Render::EditorMeshComponent",
"Id": 12826285685970138542,
"Controller": {
"Configuration": {
"ModelAsset": {
"assetId": {
"guid": "{549F4C4D-A7D9-5F2A-A6BD-F24C8BC43BBF}",
"subId": 280086017
},
"assetHint": "assets/objects/foliage/grass_flower_pink.azmodel"
}
}
}
},
"Component_[14101419970865342875]": {
"$type": "EditorPendingCompositionComponent",
"Id": 14101419970865342875
},
"Component_[14770464075286403207]": {
"$type": "EditorVisibilityComponent",
"Id": 14770464075286403207
},
"Component_[15205142653091190082]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 15205142653091190082,
"Parent Entity": "ContainerEntity"
},
"Component_[15510898811147803772]": {
"$type": "EditorInspectorComponent",
"Id": 15510898811147803772,
"ComponentOrderEntryArray": [
{
"ComponentId": 15205142653091190082
},
{
"ComponentId": 12826285685970138542,
"SortIndex": 1
}
]
},
"Component_[15988401742428977134]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 15988401742428977134
},
"Component_[4300550837037679336]": {
"$type": "EditorEntityIconComponent",
"Id": 4300550837037679336
},
"Component_[996914988793716659]": {
"$type": "SelectionComponent",
"Id": 996914988793716659
}
}
}
}
}

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:27f87510ae07771dbad3e430e31502b1d1d13dee868f143b7f1de2a7febc8eb9
size 7046400

@ -0,0 +1,8 @@
{
"values": [
{
"$type": "ScriptProcessorRule",
"scriptFilename": "Assets/TestAnim/scene_export_actor.py"
}
]
}

@ -0,0 +1,221 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project.
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
import traceback, sys, uuid, os, json
#
# Example for exporting ActorGroup scene rules
#
def log_exception_traceback():
exc_type, exc_value, exc_tb = sys.exc_info()
data = traceback.format_exception(exc_type, exc_value, exc_tb)
print(str(data))
def get_node_names(sceneGraph, nodeTypeName, testEndPoint = False, validList = None):
import azlmbr.scene.graph
import scene_api.scene_data
node = sceneGraph.get_root()
nodeList = []
children = []
paths = []
while node.IsValid():
# store children to process after siblings
if sceneGraph.has_node_child(node):
children.append(sceneGraph.get_node_child(node))
nodeName = scene_api.scene_data.SceneGraphName(sceneGraph.get_node_name(node))
paths.append(nodeName.get_path())
include = True
if (validList is not None):
include = False # if a valid list filter provided, assume to not include node name
name_parts = nodeName.get_path().split('.')
for valid in validList:
if (valid in name_parts[-1]):
include = True
break
# store any node that has provides specifc data content
nodeContent = sceneGraph.get_node_content(node)
if include and nodeContent.CastWithTypeName(nodeTypeName):
if testEndPoint is not None:
include = sceneGraph.is_node_end_point(node) is testEndPoint
if include:
if (len(nodeName.get_path())):
nodeList.append(scene_api.scene_data.SceneGraphName(sceneGraph.get_node_name(node)))
# advance to next node
if sceneGraph.has_node_sibling(node):
node = sceneGraph.get_node_sibling(node)
elif children:
node = children.pop()
else:
node = azlmbr.scene.graph.NodeIndex()
return nodeList, paths
def generate_mesh_group(scene, sceneManifest, meshDataList, paths):
# Compute the name of the scene file
clean_filename = scene.sourceFilename.replace('.', '_')
mesh_group_name = os.path.basename(clean_filename)
# make the mesh group
mesh_group = sceneManifest.add_mesh_group(mesh_group_name)
mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, clean_filename)) + '}'
# add all nodes to this mesh group
for activeMeshIndex in range(len(meshDataList)):
mesh_name = meshDataList[activeMeshIndex]
mesh_path = mesh_name.get_path()
sceneManifest.mesh_group_select_node(mesh_group, mesh_path)
def create_shape_configuration(nodeName):
import scene_api.physics_data
if(nodeName in ['_foot_','_wrist_']):
shapeConfiguration = scene_api.physics_data.BoxShapeConfiguration()
shapeConfiguration.scale = [1.1, 1.1, 1.1]
shapeConfiguration.dimensions = [2.1, 3.1, 4.1]
return shapeConfiguration
else:
shapeConfiguration = scene_api.physics_data.CapsuleShapeConfiguration()
shapeConfiguration.scale = [1.0, 1.0, 1.0]
shapeConfiguration.height = 1.0
shapeConfiguration.radius = 1.0
return shapeConfiguration
def create_collider_configuration(nodeName):
import scene_api.physics_data
colliderConfiguration = scene_api.physics_data.ColliderConfiguration()
colliderConfiguration.Position = [0.1, 0.1, 0.2]
colliderConfiguration.Rotation = [45.0, 35.0, 25.0]
return colliderConfiguration
def generate_physics_nodes(actorPhysicsSetupRule, nodeNameList):
import scene_api.physics_data
hitDetectionConfig = scene_api.physics_data.CharacterColliderConfiguration()
simulatedObjectColliderConfig = scene_api.physics_data.CharacterColliderConfiguration()
clothConfig = scene_api.physics_data.CharacterColliderConfiguration()
ragdollConfig = scene_api.physics_data.RagdollConfiguration()
for nodeName in nodeNameList:
shapeConfiguration = create_shape_configuration(nodeName)
colliderConfiguration = create_collider_configuration(nodeName)
hitDetectionConfig.add_character_collider_node_configuration_node(nodeName, colliderConfiguration, shapeConfiguration)
simulatedObjectColliderConfig.add_character_collider_node_configuration_node(nodeName, colliderConfiguration, shapeConfiguration)
clothConfig.add_character_collider_node_configuration_node(nodeName, colliderConfiguration, shapeConfiguration)
#
ragdollNode = scene_api.physics_data.RagdollNodeConfiguration()
ragdollNode.JointConfig.Name = nodeName
ragdollConfig.add_ragdoll_node_configuration(ragdollNode)
ragdollConfig.colliders.add_character_collider_node_configuration_node(nodeName, colliderConfiguration, shapeConfiguration)
actorPhysicsSetupRule.set_simulated_object_collider_config(simulatedObjectColliderConfig)
actorPhysicsSetupRule.set_hit_detection_config(hitDetectionConfig)
actorPhysicsSetupRule.set_cloth_config(clothConfig)
actorPhysicsSetupRule.set_ragdoll_config(ragdollConfig)
def generate_actor_group(scene, sceneManifest, meshDataList, paths):
import scene_api.scene_data
import scene_api.physics_data
import scene_api.actor_group
# fetch bone data
validNames = ['_neck_','_pelvis_','_leg_','_knee_','_spine_','_arm_','_clavicle_','_head_','_elbow_','_wrist_']
graph = scene_api.scene_data.SceneGraph(scene.graph)
nodeList, allNodePaths = get_node_names(graph, 'BoneData', validList = validNames)
nodeNameList = []
for activeMeshIndex, nodeName in enumerate(nodeList):
nodeNameList.append(nodeName.get_name())
# add comment
commentRule = scene_api.actor_group.CommentRule()
commentRule.text = str(nodeNameList)
# ActorPhysicsSetupRule
actorPhysicsSetupRule = scene_api.actor_group.ActorPhysicsSetupRule()
generate_physics_nodes(actorPhysicsSetupRule, nodeNameList)
# add scale of the Actor rule
actorScaleRule = scene_api.actor_group.ActorScaleRule()
actorScaleRule.scaleFactor = 2.0
# add coordinate system rule
coordinateSystemRule = scene_api.actor_group.CoordinateSystemRule()
coordinateSystemRule.useAdvancedData = False
# add morph target rule
morphTargetRule = scene_api.actor_group.MorphTargetRule()
morphTargetRule.targets.select_targets([nodeNameList[0]], nodeNameList)
# add skeleton optimization rule
skeletonOptimizationRule = scene_api.actor_group.SkeletonOptimizationRule()
skeletonOptimizationRule.autoSkeletonLOD = True
skeletonOptimizationRule.criticalBonesList.select_targets([nodeNameList[0:2]], nodeNameList)
# add LOD rule
lodRule = scene_api.actor_group.LodRule()
lodRule0 = lodRule.add_lod_level(0)
lodRule0.select_targets([nodeNameList[1:4]], nodeNameList)
actorGroup = scene_api.actor_group.ActorGroup()
actorGroup.name = os.path.basename(scene.sourceFilename)
actorGroup.add_rule(actorScaleRule)
actorGroup.add_rule(coordinateSystemRule)
actorGroup.add_rule(skeletonOptimizationRule)
actorGroup.add_rule(morphTargetRule)
actorGroup.add_rule(lodRule)
actorGroup.add_rule(actorPhysicsSetupRule)
actorGroup.add_rule(commentRule)
sceneManifest.manifest['values'].append(actorGroup.to_dict())
def update_manifest(scene):
import json, uuid, os
import azlmbr.scene.graph
import scene_api.scene_data
graph = scene_api.scene_data.SceneGraph(scene.graph)
mesh_name_list, all_node_paths = get_node_names(graph, 'MeshData')
scene_manifest = scene_api.scene_data.SceneManifest()
generate_actor_group(scene, scene_manifest, mesh_name_list, all_node_paths)
generate_mesh_group(scene, scene_manifest, mesh_name_list, all_node_paths)
# Convert the manifest to a JSON string and return it
return scene_manifest.export()
sceneJobHandler = None
def on_update_manifest(args):
try:
scene = args[0]
return update_manifest(scene)
except RuntimeError as err:
print (f'ERROR - {err}')
log_exception_traceback()
except:
log_exception_traceback()
global sceneJobHandler
sceneJobHandler.disconnect()
sceneJobHandler = None
# try to create SceneAPI handler for processing
try:
import azlmbr.scene
sceneJobHandler = azlmbr.scene.ScriptBuildingNotificationBusHandler()
sceneJobHandler.connect()
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
except:
sceneJobHandler = None

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:011454252e40c927343cce16296412f02f45d1f345c75c036651bdcca473bda5
size 2672

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7bafcc4aefab827e1414e64bcdde235500b51392e52c9ccd588b2d7a24b865a0
size 20214

@ -0,0 +1,124 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project.
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
import os, traceback, binascii, sys, json, pathlib, logging
import azlmbr.math
import azlmbr.bus
from scene_helpers import *
#
# SceneAPI Processor
#
def update_manifest(scene):
import uuid
import azlmbr.scene as sceneApi
import azlmbr.scene.graph
from scene_api import scene_data as sceneData
graph = sceneData.SceneGraph(scene.graph)
# Get a list of all the mesh nodes, as well as all the nodes
mesh_name_list, all_node_paths = get_mesh_node_names(graph)
mesh_name_list.sort(key=lambda node: str.casefold(node.get_path()))
scene_manifest = sceneData.SceneManifest()
clean_filename = scene.sourceFilename.replace('.', '_')
# Compute the filename of the scene file
source_basepath = scene.watchFolder
source_relative_path = os.path.dirname(os.path.relpath(clean_filename, source_basepath))
source_filename_only = os.path.basename(clean_filename)
created_entities = []
previous_entity_id = azlmbr.entity.InvalidEntityId
first_mesh = True
# Make a list of mesh node paths
mesh_path_list = list(map(lambda node: node.get_path(), mesh_name_list))
# Assume the first mesh is the main mesh
main_mesh = mesh_name_list[0]
mesh_path = main_mesh.get_path()
# Create a unique mesh group name using the filename + node name
mesh_group_name = '{}_{}'.format(source_filename_only, main_mesh.get_name())
# Remove forbidden filename characters from the name since this will become a file on disk later
mesh_group_name = "".join(char for char in mesh_group_name if char not in "|<>:\"/?*\\")
# Add the MeshGroup to the manifest and give it a unique ID
mesh_group = scene_manifest.add_mesh_group(mesh_group_name)
mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, source_filename_only + mesh_path)) + '}'
# Set our current node as the only node that is included in this MeshGroup
scene_manifest.mesh_group_select_node(mesh_group, mesh_path)
# Explicitly remove all other nodes to prevent implicit inclusions
for node in mesh_path_list:
if node != mesh_path:
scene_manifest.mesh_group_unselect_node(mesh_group, node)
# Create a LOD rule
lod_rule = scene_manifest.mesh_group_add_lod_rule(mesh_group)
# Loop all the mesh nodes after the first
for x in mesh_path_list[1:]:
# Add a new LOD level
lod = scene_manifest.lod_rule_add_lod(lod_rule)
# Select the current mesh for this LOD level
scene_manifest.lod_select_node(lod, x)
# Unselect every other mesh for this LOD level
for y in mesh_path_list:
if y != x:
scene_manifest.lod_unselect_node(lod, y)
# Create an editor entity
entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name)
# Add an EditorMeshComponent to the entity
editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "AZ::Render::EditorMeshComponent")
# Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just created + the azmodel extension
# The MeshGroup we created will be output as a product in the asset's path named mesh_group_name.azmodel
# The assetHint will be converted to an AssetId later during prefab loading
json_update = json.dumps({
"Controller": { "Configuration": { "ModelAsset": {
"assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel" }}}
});
# Apply the JSON above to the component we created
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_mesh_component, json_update)
if not result:
raise RuntimeError("UpdateComponentForEntity failed for Mesh component")
create_prefab(scene_manifest, source_filename_only, [entity_id])
# Convert the manifest to a JSON string and return it
new_manifest = scene_manifest.export()
return new_manifest
sceneJobHandler = None
def on_update_manifest(args):
try:
scene = args[0]
return update_manifest(scene)
except RuntimeError as err:
print (f'ERROR - {err}')
log_exception_traceback()
except:
log_exception_traceback()
global sceneJobHandler
sceneJobHandler = None
# try to create SceneAPI handler for processing
try:
import azlmbr.scene as sceneApi
if (sceneJobHandler == None):
sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler()
sceneJobHandler.connect()
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
except:
sceneJobHandler = None

@ -0,0 +1,95 @@
"""
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 traceback, logging, json
from typing import Tuple, List
import azlmbr.bus
from scene_api import scene_data as sceneData
from scene_api.scene_data import SceneGraphName
def log_exception_traceback():
"""
Outputs an exception stacktrace.
"""
data = traceback.format_exc()
logger = logging.getLogger('python')
logger.error(data)
def sanitize_name_for_disk(name: str):
"""
Removes illegal filename characters from a string.
:param name: String to clean.
:return: Name with illegal characters removed.
"""
return "".join(char for char in name if char not in "|<>:\"/?*\\")
def get_mesh_node_names(scene_graph: sceneData.SceneGraph) -> Tuple[List[SceneGraphName], List[str]]:
"""
Returns a tuple of all the mesh nodes as well as all the node paths
:param scene_graph: Scene graph to search
:return: Tuple of [Mesh Nodes, All Node Paths]
"""
import azlmbr.scene as sceneApi
import azlmbr.scene.graph
mesh_data_list = []
node = scene_graph.get_root()
children = []
paths = []
while node.IsValid():
# store children to process after siblings
if scene_graph.has_node_child(node):
children.append(scene_graph.get_node_child(node))
node_name = sceneData.SceneGraphName(scene_graph.get_node_name(node))
paths.append(node_name.get_path())
# store any node that has mesh data content
node_content = scene_graph.get_node_content(node)
if node_content.CastWithTypeName('MeshData'):
if scene_graph.is_node_end_point(node) is False:
if len(node_name.get_path()):
mesh_data_list.append(sceneData.SceneGraphName(scene_graph.get_node_name(node)))
# advance to next node
if scene_graph.has_node_sibling(node):
node = scene_graph.get_node_sibling(node)
elif children:
node = children.pop()
else:
node = azlmbr.scene.graph.NodeIndex()
return mesh_data_list, paths
def create_prefab(scene_manifest: sceneData.SceneManifest, prefab_name: str, entities: list) -> None:
prefab_filename = prefab_name + ".prefab"
created_template_id = azlmbr.prefab.PrefabSystemScriptingBus(azlmbr.bus.Broadcast, "CreatePrefab", entities,
prefab_filename)
if created_template_id is None or created_template_id == azlmbr.prefab.InvalidTemplateId:
raise RuntimeError("CreatePrefab {} failed".format(prefab_filename))
# Convert the prefab to a JSON string
output = azlmbr.prefab.PrefabLoaderScriptingBus(azlmbr.bus.Broadcast, "SaveTemplateToString", created_template_id)
if output is not None and output.IsSuccess():
json_string = output.GetValue()
uuid = azlmbr.math.Uuid_CreateRandom().ToString()
json_result = json.loads(json_string)
# Add a PrefabGroup to the manifest and store the JSON on it
scene_manifest.add_prefab_group(prefab_name, uuid, json_result)
else:
raise RuntimeError(
"SaveTemplateToString failed for template id {}, prefab {}".format(created_template_id, prefab_filename))

@ -5,55 +5,17 @@
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
import os, traceback, binascii, sys, json, pathlib, logging
import azlmbr.math
import azlmbr.bus
import azlmbr.math
from scene_api.scene_data import PrimitiveShape, DecompositionMode
from scene_helpers import *
#
# SceneAPI Processor
#
def log_exception_traceback():
data = traceback.format_exc()
logger = logging.getLogger('python')
logger.error(data)
def get_mesh_node_names(sceneGraph):
import azlmbr.scene as sceneApi
import azlmbr.scene.graph
from scene_api import scene_data as sceneData
meshDataList = []
node = sceneGraph.get_root()
children = []
paths = []
while node.IsValid():
# store children to process after siblings
if sceneGraph.has_node_child(node):
children.append(sceneGraph.get_node_child(node))
nodeName = sceneData.SceneGraphName(sceneGraph.get_node_name(node))
paths.append(nodeName.get_path())
# store any node that has mesh data content
nodeContent = sceneGraph.get_node_content(node)
if nodeContent.CastWithTypeName('MeshData'):
if sceneGraph.is_node_end_point(node) is False:
if (len(nodeName.get_path())):
meshDataList.append(sceneData.SceneGraphName(sceneGraph.get_node_name(node)))
# advance to next node
if sceneGraph.has_node_sibling(node):
node = sceneGraph.get_node_sibling(node)
elif children:
node = children.pop()
else:
node = azlmbr.scene.graph.NodeIndex()
return meshDataList, paths
def add_material_component(entity_id):
# Create an override AZ::Render::EditorMaterialComponent
editor_material_component = azlmbr.entity.EntityUtilityBus(
@ -64,24 +26,53 @@ def add_material_component(entity_id):
# this fills out the material asset to a known product AZMaterial asset relative path
json_update = json.dumps({
"Controller": { "Configuration": { "materials": [
"Controller": {"Configuration": {"materials": [
{
"Key": {},
"Value": { "MaterialAsset":{
"Value": {"MaterialAsset": {
"assetHint": "materials/basic_grey.azmaterial"
}}
}]
}}
});
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_material_component, json_update)
})
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id,
editor_material_component, json_update)
if not result:
raise RuntimeError("UpdateComponentForEntity for editor_material_component failed")
def add_physx_meshes(scene_manifest: sceneData.SceneManifest, source_file_name: str, mesh_name_list: List, all_node_paths: List[str]):
first_mesh = mesh_name_list[0].get_path()
# Add a Box Primitive PhysX mesh with a comment
physx_box = scene_manifest.add_physx_primitive_mesh_group(source_file_name + "_box", PrimitiveShape.BOX, 0.0, None)
scene_manifest.physx_mesh_group_add_comment(physx_box, "This is a box primitive")
# Select the first mesh, unselect every other node
scene_manifest.physx_mesh_group_add_selected_node(physx_box, first_mesh)
for node in all_node_paths:
if node != first_mesh:
scene_manifest.physx_mesh_group_add_unselected_node(physx_box, node)
# Add a Convex Mesh PhysX mesh with a comment
convex_mesh = scene_manifest.add_physx_convex_mesh_group(source_file_name + "_convex", 0.08, .0004,
True, True, True, True, True, 24, True, "Glass")
scene_manifest.physx_mesh_group_add_comment(convex_mesh, "This is a convex mesh")
# Select/Unselect nodes using lists
all_except_first_mesh = [x for x in all_node_paths if x != first_mesh]
scene_manifest.physx_mesh_group_add_selected_unselected_nodes(convex_mesh, [first_mesh], all_except_first_mesh)
# Configure mesh decomposition for this mesh
scene_manifest.physx_mesh_group_decompose_meshes(convex_mesh, 512, 32, .002, 100100, DecompositionMode.TETRAHEDRON,
0.06, 0.055, 0.00015, 3, 3, True, False)
# Add a Triangle mesh
triangle = scene_manifest.add_physx_triangle_mesh_group(source_file_name + "_triangle", False, True, True, True, True, True)
scene_manifest.physx_mesh_group_add_selected_unselected_nodes(triangle, [first_mesh], all_except_first_mesh)
def update_manifest(scene):
import json
import uuid, os
import azlmbr.scene as sceneApi
import azlmbr.scene.graph
from scene_api import scene_data as sceneData
@ -101,6 +92,8 @@ def update_manifest(scene):
previous_entity_id = azlmbr.entity.InvalidEntityId
first_mesh = True
add_physx_meshes(scene_manifest, source_filename_only, mesh_name_list, all_node_paths)
# Loop every mesh node in the scene
for activeMeshIndex in range(len(mesh_name_list)):
mesh_name = mesh_name_list[activeMeshIndex]
@ -108,7 +101,7 @@ def update_manifest(scene):
# Create a unique mesh group name using the filename + node name
mesh_group_name = '{}_{}'.format(source_filename_only, mesh_name.get_name())
# Remove forbidden filename characters from the name since this will become a file on disk later
mesh_group_name = "".join(char for char in mesh_group_name if char not in "|<>:\"/?*\\")
mesh_group_name = sanitize_name_for_disk(mesh_group_name)
# Add the MeshGroup to the manifest and give it a unique ID
mesh_group = scene_manifest.add_mesh_group(mesh_group_name)
mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, source_filename_only + mesh_path)) + '}'
@ -129,37 +122,61 @@ def update_manifest(scene):
# Create an editor entity
entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name)
# Add an EditorMeshComponent to the entity
editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "AZ::Render::EditorMeshComponent")
# Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just created + the azmodel extension
# The MeshGroup we created will be output as a product in the asset's path named mesh_group_name.azmodel
# The assetHint will be converted to an AssetId later during prefab loading
editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName",
entity_id, "AZ::Render::EditorMeshComponent")
# Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just
# created + the azmodel extension The MeshGroup we created will be output as a product in the asset's path
# named mesh_group_name.azmodel The assetHint will be converted to an AssetId later during prefab loading
json_update = json.dumps({
"Controller": { "Configuration": { "ModelAsset": {
"assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel" }}}
});
"Controller": {"Configuration": {"ModelAsset": {
"assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel"}}}
})
# Apply the JSON above to the component we created
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_mesh_component, json_update)
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id,
editor_mesh_component, json_update)
if not result:
raise RuntimeError("UpdateComponentForEntity failed for Mesh component")
# Add a physics component referencing the triangle mesh we made for the first node
if previous_entity_id is None:
physx_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName",
entity_id, "{FD429282-A075-4966-857F-D0BBF186CFE6} EditorColliderComponent")
json_update = json.dumps({
"ShapeConfiguration": {
"PhysicsAsset": {
"Asset": {
"assetHint": os.path.join(source_relative_path, source_filename_only + "_triangle.pxmesh")
}
}
}
})
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, physx_mesh_component, json_update)
if not result:
raise RuntimeError("UpdateComponentForEntity failed for PhysX mesh component")
# an example of adding a material component to override the default material
if previous_entity_id is not None and first_mesh:
first_mesh = False
add_material_component(entity_id)
# Get the transform component
transform_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0")
transform_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName",
entity_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0")
# Set this entity to be a child of the last entity we created
# This is just an example of how to do parenting and isn't necessarily useful to parent everything like this
if previous_entity_id is not None:
transform_json = json.dumps({
"Parent Entity" : previous_entity_id.to_json()
});
"Parent Entity": previous_entity_id.to_json()
})
# Apply the JSON update
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, transform_component, transform_json)
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id,
transform_component, transform_json)
if not result:
raise RuntimeError("UpdateComponentForEntity failed for Transform component")
@ -171,37 +188,23 @@ def update_manifest(scene):
created_entities.append(entity_id)
# Create a prefab with all our entities
prefab_filename = source_filename_only + ".prefab"
created_template_id = azlmbr.prefab.PrefabSystemScriptingBus(azlmbr.bus.Broadcast, "CreatePrefab", created_entities, prefab_filename)
if created_template_id == azlmbr.prefab.InvalidTemplateId:
raise RuntimeError("CreatePrefab {} failed".format(prefab_filename))
# Convert the prefab to a JSON string
output = azlmbr.prefab.PrefabLoaderScriptingBus(azlmbr.bus.Broadcast, "SaveTemplateToString", created_template_id)
if output.IsSuccess():
jsonString = output.GetValue()
uuid = azlmbr.math.Uuid_CreateRandom().ToString()
jsonResult = json.loads(jsonString)
# Add a PrefabGroup to the manifest and store the JSON on it
scene_manifest.add_prefab_group(source_filename_only, uuid, jsonResult)
else:
raise RuntimeError("SaveTemplateToString failed for template id {}, prefab {}".format(created_template_id, prefab_filename))
create_prefab(scene_manifest, source_filename_only, created_entities)
# Convert the manifest to a JSON string and return it
new_manifest = scene_manifest.export()
return new_manifest
sceneJobHandler = None
def on_update_manifest(args):
try:
scene = args[0]
return update_manifest(scene)
except RuntimeError as err:
print (f'ERROR - {err}')
print(f'ERROR - {err}')
log_exception_traceback()
except:
log_exception_traceback()
@ -209,10 +212,12 @@ def on_update_manifest(args):
global sceneJobHandler
sceneJobHandler = None
# try to create SceneAPI handler for processing
try:
import azlmbr.scene as sceneApi
if (sceneJobHandler == None):
if sceneJobHandler is None:
sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler()
sceneJobHandler.connect()
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)

@ -47,18 +47,4 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED)
COMPONENT
Atom
)
ly_add_pytest(
NAME AutomatedTesting::Atom_TestSuite_Main_GPU_Optimized
TEST_SUITE main
TEST_REQUIRES gpu
TEST_SERIAL
TIMEOUT 1200
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_GPU_Optimized.py
RUNTIME_DEPENDENCIES
AssetProcessor
AutomatedTesting.Assets
Editor
COMPONENT
Atom
)
endif()

@ -4,129 +4,82 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import datetime
import logging
import os
import zipfile
import pytest
import editor_python_test_tools.hydra_test_utils as hydra
import ly_test_tools.environment.file_system as file_system
from ly_test_tools.benchmark.data_aggregator import BenchmarkDataAggregator
from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
from Atom.atom_utils.atom_component_helper import compare_screenshot_to_golden_image, golden_images_directory
import editor_python_test_tools.hydra_test_utils as hydra
from .atom_utils.atom_component_helper import compare_screenshot_similarity, ImageComparisonTestFailure
logger = logging.getLogger(__name__)
DEFAULT_SUBFOLDER_PATH = 'user/PythonTests/Automated/Screenshots'
TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests")
def golden_images_directory():
"""
Uses this file location to return the valid location for golden image files.
:return: The path to the golden_images directory, but raises an IOError if the golden_images directory is missing.
"""
current_file_directory = os.path.join(os.path.dirname(__file__))
golden_images_dir = os.path.join(current_file_directory, 'golden_images')
if not os.path.exists(golden_images_dir):
raise IOError(
f'golden_images" directory was not found at path "{golden_images_dir}"'
f'Please add a "golden_images" directory inside: "{current_file_directory}"'
)
return golden_images_dir
def create_screenshots_archive(screenshot_path):
"""
Creates a new zip file archive at archive_path containing all files listed within archive_path.
:param screenshot_path: location containing the files to archive, the zip archive file will also be saved here.
:return: None, but creates a new zip file archive inside path containing all of the files inside archive_path.
"""
files_to_archive = []
# Search for .png and .ppm files to add to the zip archive file.
for (folder_name, sub_folders, file_names) in os.walk(screenshot_path):
for file_name in file_names:
if file_name.endswith(".png") or file_name.endswith(".ppm"):
file_path = os.path.join(folder_name, file_name)
files_to_archive.append(file_path)
# Setup variables for naming the zip archive file.
timestamp = datetime.datetime.now().timestamp()
formatted_timestamp = datetime.datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d_%H-%M-%S")
screenshots_file = os.path.join(screenshot_path, f'zip_archive_{formatted_timestamp}.zip')
# Write all of the valid .png and .ppm files to the archive file.
with zipfile.ZipFile(screenshots_file, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zip_archive:
for file_path in files_to_archive:
file_name = os.path.basename(file_path)
zip_archive.write(file_path, file_name)
logger = logging.getLogger(__name__)
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ["windows_editor"])
@pytest.mark.parametrize("level", ["Base"])
class TestAllComponentsIndepthTests(object):
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestAutomation(EditorTestSuite):
# Remove -autotest_mode from global_extra_cmdline_args since we need rendering for these tests.
global_extra_cmdline_args = ["-BatchMode"] # Default is ["-BatchMode", "-autotest_mode"]
enable_prefab_system = False
@pytest.mark.parametrize("screenshot_name", ["AtomBasicLevelSetup.ppm"])
@pytest.mark.test_case_id("C34603773")
def test_BasicLevelSetup_SetsUpLevel(
self, request, editor, workspace, project, launcher_platform, level, screenshot_name):
"""
Please review the hydra script run by this test for more specific test info.
Tests that a basic rendering level setup can be created (lighting, meshes, materials, etc.).
"""
class AtomGPU_BasicLevelSetup_SetsUpLevel(EditorSharedTest):
use_null_renderer = False # Default is True
screenshot_name = "AtomBasicLevelSetup.ppm"
test_screenshots = [] # Gets set by setup()
screenshot_directory = "" # Gets set by setup()
# Clear existing test screenshots before starting test.
def setup(self, workspace):
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
test_screenshots = [os.path.join(screenshot_directory, screenshot_name)]
test_screenshots = [os.path.join(screenshot_directory, self.screenshot_name)]
file_system.delete(test_screenshots, True, True)
golden_images = [os.path.join(golden_images_directory(), screenshot_name)]
level_creation_expected_lines = [
"Viewport is set to the expected size: True",
"Exited game mode"
]
unexpected_lines = ["Traceback (most recent call last):"]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_GPUTest_BasicLevelSetup.py",
timeout=180,
expected_lines=level_creation_expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
cfg_args=[level],
null_renderer=False,
enable_prefab_system=False,
)
from Atom.tests import hydra_AtomGPU_BasicLevelSetup as test_module
similarity_threshold = 0.99
for test_screenshot, golden_image in zip(test_screenshots, golden_images):
screenshot_comparison_result = compare_screenshot_similarity(
test_screenshot, golden_image, similarity_threshold, True, screenshot_directory)
if screenshot_comparison_result != "Screenshots match":
raise Exception(f"Screenshot test failed: {screenshot_comparison_result}")
assert compare_screenshot_to_golden_image(screenshot_directory, test_screenshots, golden_images, 0.99) is True
@pytest.mark.test_case_id("C34525095")
def test_LightComponent_ScreenshotMatchesGoldenImage(
self, request, editor, workspace, project, launcher_platform, level):
"""
Please review the hydra script run by this test for more specific test info.
Tests that the Light component screenshots in a rendered level appear the same as the golden images.
"""
class AtomGPU_LightComponent_AreaLightScreenshotsMatchGoldenImages(EditorSharedTest):
use_null_renderer = False # Default is True
screenshot_names = [
"AreaLight_1.ppm",
"AreaLight_2.ppm",
"AreaLight_3.ppm",
"AreaLight_4.ppm",
"AreaLight_5.ppm",
]
test_screenshots = [] # Gets set by setup()
screenshot_directory = "" # Gets set by setup()
# Clear existing test screenshots before starting test.
def setup(self, workspace):
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
for screenshot in self.screenshot_names:
screenshot_path = os.path.join(screenshot_directory, screenshot)
self.test_screenshots.append(screenshot_path)
file_system.delete(self.test_screenshots, True, True)
golden_images = []
for golden_image in screenshot_names:
golden_image_path = os.path.join(golden_images_directory(), golden_image)
golden_images.append(golden_image_path)
from Atom.tests import hydra_AtomGPU_AreaLightScreenshotTest as test_module
assert compare_screenshot_to_golden_image(screenshot_directory, test_screenshots, golden_images, 0.99) is True
@pytest.mark.test_case_id("C34525110")
class AtomGPU_LightComponent_SpotLightScreenshotsMatchGoldenImages(EditorSharedTest):
use_null_renderer = False # Default is True
screenshot_names = [
"SpotLight_1.ppm",
"SpotLight_2.ppm",
"SpotLight_3.ppm",
@ -134,40 +87,25 @@ class TestAllComponentsIndepthTests(object):
"SpotLight_5.ppm",
"SpotLight_6.ppm",
]
test_screenshots = [] # Gets set by setup()
screenshot_directory = "" # Gets set by setup()
# Clear existing test screenshots before starting test.
def setup(self, workspace):
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
test_screenshots = []
for screenshot in screenshot_names:
for screenshot in self.screenshot_names:
screenshot_path = os.path.join(screenshot_directory, screenshot)
test_screenshots.append(screenshot_path)
file_system.delete(test_screenshots, True, True)
self.test_screenshots.append(screenshot_path)
file_system.delete(self.test_screenshots, True, True)
golden_images = []
for golden_image in screenshot_names:
golden_image_path = os.path.join(golden_images_directory(), golden_image)
golden_images.append(golden_image_path)
expected_lines = ["spot_light Controller|Configuration|Shadows|Shadowmap size: SUCCESS"]
unexpected_lines = ["Traceback (most recent call last):"]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_GPUTest_LightComponent.py",
timeout=180,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
cfg_args=[level],
null_renderer=False,
enable_prefab_system=False,
)
from Atom.tests import hydra_AtomGPU_SpotLightScreenshotTest as test_module
similarity_threshold = 0.99
for test_screenshot, golden_image in zip(test_screenshots, golden_images):
screenshot_comparison_result = compare_screenshot_similarity(
test_screenshot, golden_image, similarity_threshold, True, screenshot_directory)
if screenshot_comparison_result != "Screenshots match":
raise ImageComparisonTestFailure(f"Screenshot test failed: {screenshot_comparison_result}")
assert compare_screenshot_to_golden_image(screenshot_directory, test_screenshots, golden_images, 0.99) is True
@pytest.mark.parametrize('rhi', ['dx12', 'vulkan'])
@ -198,7 +136,7 @@ class TestPerformanceBenchmarkSuite(object):
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
os.path.join(os.path.dirname(__file__), "tests"),
editor,
"hydra_GPUTest_AtomFeatureIntegrationBenchmark.py",
timeout=600,
@ -235,7 +173,7 @@ class TestMaterialEditor(object):
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
os.path.join(os.path.dirname(__file__), "tests"),
generic_launcher,
editor_script="",
run_python="--runpython",

@ -1,46 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import pytest
import ly_test_tools.environment.file_system as file_system
from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots
from .atom_utils.atom_component_helper import create_screenshots_archive, golden_images_directory
DEFAULT_SUBFOLDER_PATH = 'user/PythonTests/Automated/Screenshots'
@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):
# Remove -autotest_mode from global_extra_cmdline_args since we need rendering for these tests.
global_extra_cmdline_args = ["-BatchMode"] # Default is ["-BatchMode", "-autotest_mode"]
enable_prefab_system = False
@pytest.mark.test_case_id("C34603773")
class AtomGPU_BasicLevelSetup_SetsUpLevel(EditorSharedTest):
use_null_renderer = False # Default is True
screenshot_name = "AtomBasicLevelSetup.ppm"
test_screenshots = [] # Gets set by setup()
screenshot_directory = "" # Gets set by setup()
# Clear existing test screenshots before starting test.
def setup(self, workspace):
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
test_screenshots = [os.path.join(screenshot_directory, self.screenshot_name)]
file_system.delete(test_screenshots, True, True)
from Atom.tests import hydra_AtomGPU_BasicLevelSetup as test_module
golden_images = [os.path.join(golden_images_directory(), screenshot_name)]
for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images):
compare_screenshots(test_screenshot, golden_screenshot)
create_screenshots_archive(screenshot_directory)

@ -12,6 +12,7 @@ import pytest
import ly_test_tools.environment.file_system as file_system
import editor_python_test_tools.hydra_test_utils as hydra
from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
from Atom.atom_utils.atom_constants import LIGHT_TYPES
logger = logging.getLogger(__name__)
@ -159,3 +160,17 @@ class TestMaterialEditorBasicTests(object):
enable_prefab_system=False,
)
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestAutomation(EditorTestSuite):
enable_prefab_system = False
@pytest.mark.test_case_id("C36529666")
class AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded as test_module
@pytest.mark.test_case_id("C36525660")
class AtomEditorComponentsLevel_DisplayMapperAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponentsLevel_DisplayMapperAdded as test_module

@ -93,161 +93,165 @@ def compare_screenshot_similarity(
return result
def create_basic_atom_level(level_name):
def compare_screenshot_to_golden_image(
screenshot_directory, test_screenshots, golden_images, similarity_threshold=0.99):
"""
Creates a new level inside the Editor matching level_name & adds the following:
1. "default_level" entity to hold all other entities.
2. Adds Grid, Global Skylight (IBL), ground Mesh, Directional Light, Sphere w/ material+mesh, & Camera components.
3. Each of these components has its settings tweaked slightly to match the ideal scene to test Atom rendering.
:param level_name: name of the level to create and apply this basic setup to.
Compares a list of test_screenshots to a list of golden_images and return True if they match within the
similarity threshold set. Otherwise, it will raise ImageComparisonTestFailure with a failure message.
:param screenshot_directory: path to the directory containing screenshots for creating .zip archives.
:param test_screenshots: list of test screenshot path strings.
:param golden_images: list of golden image path strings.
:param similarity_threshold: float threshold tolerance to set when comparing screenshots to golden images.
"""
for test_screenshot, golden_image in zip(test_screenshots, golden_images):
screenshot_comparison_result = compare_screenshot_similarity(
test_screenshot, golden_image, similarity_threshold, True, screenshot_directory)
if screenshot_comparison_result != "Screenshots match":
raise ImageComparisonTestFailure(f"Screenshot test failed: {screenshot_comparison_result}")
return True
def initial_viewport_setup(screen_width=1280, screen_height=720):
"""
For setting up the initial viewport resolution to expected default values before running a screenshot test.
Defaults to 1280 x 720 resolution (in pixels).
:param screen_width: Width in pixels to set the viewport width size to.
:param screen_height: Height in pixels to set the viewport height size to.
:return: None
"""
import azlmbr.legacy.general as general
general.set_viewport_size(screen_width, screen_height)
general.update_viewport()
def enter_exit_game_mode_take_screenshot(screenshot_name, enter_game_tuple, exit_game_tuple, timeout_in_seconds=4):
"""
Enters game mode, takes a screenshot named screenshot_name (must include file extension), and exits game mode.
:param screenshot_name: string representing the name of the screenshot file, including file extension.
:param enter_game_tuple: tuple where the 1st string is success & 2nd string is failure for entering the game.
:param exit_game_tuple: tuple where the 1st string is success & 2nd string is failure for exiting the game.
:param timeout_in_seconds: int or float seconds to wait for entering/exiting game mode.
:return: None
"""
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.camera as camera
import azlmbr.editor as editor
import azlmbr.entity as entity
import azlmbr.legacy.general as general
from editor_python_test_tools.utils import TestHelper
from Atom.atom_utils.screenshot_utils import ScreenshotHelper
TestHelper.enter_game_mode(enter_game_tuple)
TestHelper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=timeout_in_seconds)
ScreenshotHelper(general.idle_wait_frames).capture_screenshot_blocking(screenshot_name)
TestHelper.exit_game_mode(exit_game_tuple)
TestHelper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=timeout_in_seconds)
def create_basic_atom_rendering_scene():
"""
Sets up a new scene inside the Editor for testing Atom rendering GPU output.
Setup: Deletes all existing entities before creating the scene.
The created scene includes:
1. "Default Level" entity that holds all of the other entities.
2. "Grid" entity: Contains a Grid component.
3. "Global Skylight (IBL)" entity: Contains HDRI Skybox & Global Skylight (IBL) components.
4. "Ground Plane" entity: Contains Material & Mesh components.
5. "Directional Light" entity: Contains Directional Light component.
6. "Sphere" entity: Contains Material & Mesh components.
7. "Camera" entity: Contains Camera component.
:return: None
"""
import azlmbr.math as math
import azlmbr.object
import azlmbr.paths
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.editor_test_helper import EditorTestHelper
from editor_python_test_tools.asset_utils import Asset
from editor_python_test_tools.editor_entity_utils import EditorEntity
helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper")
from Atom.atom_utils.atom_constants import AtomComponentProperties
# Wait for Editor idle loop before executing Python hydra scripts.
general.idle_enable(True)
DEGREE_RADIAN_FACTOR = 0.0174533
# Basic setup for opened level.
helper.open_level(level_name="Base")
general.idle_wait(1.0)
general.update_viewport()
general.idle_wait(0.5) # half a second is more than enough for updating the viewport.
# Close out problematic windows, FPS meters, and anti-aliasing.
if general.is_helpers_shown(): # Turn off the helper gizmos if visible
general.toggle_helpers()
general.idle_wait(1.0)
if general.is_pane_visible("Error Report"): # Close Error Report windows that block focus.
general.close_pane("Error Report")
if general.is_pane_visible("Error Log"): # Close Error Log windows that block focus.
general.close_pane("Error Log")
general.idle_wait(1.0)
general.run_console("r_displayInfo=0")
general.idle_wait(1.0)
# Delete all existing entities & create default_level entity
# Setup: Deletes all existing entities before creating the scene.
search_filter = azlmbr.entity.SearchFilter()
all_entities = entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter)
editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntities", all_entities)
default_level = hydra.Entity("default_level")
default_position = math.Vector3(0.0, 0.0, 0.0)
default_level.create_entity(default_position, ["Grid"])
default_level.get_set_test(0, "Controller|Configuration|Secondary Grid Spacing", 1.0)
# Set the viewport up correctly after adding the parent default_level entity.
screen_width = 1280
screen_height = 720
degree_radian_factor = 0.0174533 # Used by "Rotation" property for the Transform component.
general.set_viewport_size(screen_width, screen_height)
general.update_viewport()
helper.wait_for_condition(
function=lambda: helper.isclose(a=general.get_viewport_size().x, b=screen_width, rel_tol=0.1)
and helper.isclose(a=general.get_viewport_size().y, b=screen_height, rel_tol=0.1),
timeout_in_seconds=4.0
)
result = helper.isclose(a=general.get_viewport_size().x, b=screen_width, rel_tol=0.1) and helper.isclose(
a=general.get_viewport_size().y, b=screen_height, rel_tol=0.1)
general.log(general.get_viewport_size().x)
general.log(general.get_viewport_size().y)
general.log(general.get_viewport_size().z)
general.log(f"Viewport is set to the expected size: {result}")
general.log("Basic level created")
general.run_console("r_DisplayInfo = 0")
# Create global_skylight entity and set the properties
global_skylight = hydra.Entity("global_skylight")
global_skylight.create_entity(
entity_position=default_position,
components=["HDRi Skybox", "Global Skylight (IBL)"],
parent_id=default_level.id)
global_skylight_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage")
global_skylight_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", global_skylight_asset_path, math.Uuid(), False)
global_skylight.get_set_test(0, "Controller|Configuration|Cubemap Texture", global_skylight_asset_value)
global_skylight.get_set_test(1, "Controller|Configuration|Diffuse Image", global_skylight_asset_value)
global_skylight.get_set_test(1, "Controller|Configuration|Specular Image", global_skylight_asset_value)
# Create ground_plane entity and set the properties
ground_plane = hydra.Entity("ground_plane")
ground_plane.create_entity(
entity_position=default_position,
components=["Material"],
parent_id=default_level.id)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", ground_plane.id, 32.0)
# Work around to add the correct Atom Mesh component and asset.
mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId
ground_plane.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", ground_plane.id, [mesh_type_id]
).GetValue()[0]
)
all_entities = azlmbr.entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter)
azlmbr.editor.ToolsApplicationRequestBus(azlmbr.bus.Broadcast, "DeleteEntities", all_entities)
# 1. "Default Level" entity that holds all of the other entities.
default_level_entity_name = "Default Level"
default_level_entity = EditorEntity.create_editor_entity_at(math.Vector3(0.0, 0.0, 0.0), default_level_entity_name)
# 2. "Grid" entity: Contains a Grid component.
grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.grid(), default_level_entity.id)
grid_component = grid_entity.add_component(AtomComponentProperties.grid())
secondary_grid_spacing_value = 1.0
grid_component.set_component_property_value(
AtomComponentProperties.grid('Secondary Grid Spacing'), secondary_grid_spacing_value)
# 3. "Global Skylight (IBL)" entity: Contains HDRI Skybox & Global Skylight (IBL) components.
global_skylight_entity = EditorEntity.create_editor_entity(
AtomComponentProperties.global_skylight(), default_level_entity.id)
hdri_skybox_component = global_skylight_entity.add_component(AtomComponentProperties.hdri_skybox())
global_skylight_component = global_skylight_entity.add_component(AtomComponentProperties.global_skylight())
global_skylight_image_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage")
global_skylight_image_asset = Asset.find_asset_by_path(global_skylight_image_asset_path, False)
hdri_skybox_component.set_component_property_value(
AtomComponentProperties.hdri_skybox('Cubemap Texture'), global_skylight_image_asset.id)
global_skylight_diffuse_image_asset_path = os.path.join(
"LightingPresets", "default_iblskyboxcm_ibldiffuse.exr.streamingimage")
global_skylight_diffuse_image_asset = Asset.find_asset_by_path(global_skylight_diffuse_image_asset_path, False)
global_skylight_component.set_component_property_value(
AtomComponentProperties.global_skylight('Diffuse Image'), global_skylight_diffuse_image_asset.id)
global_skylight_specular_image_asset_path = os.path.join(
"LightingPresets", "default_iblskyboxcm_iblspecular.exr.streamingimage")
global_skylight_specular_image_asset = Asset.find_asset_by_path(
global_skylight_specular_image_asset_path, False)
global_skylight_component.set_component_property_value(
AtomComponentProperties.global_skylight('Specular Image'), global_skylight_specular_image_asset.id)
# 4. "Ground Plane" entity: Contains Material & Mesh components.
ground_plane_name = "Ground Plane"
ground_plane_entity = EditorEntity.create_editor_entity(ground_plane_name, default_level_entity.id)
ground_plane_material_component = ground_plane_entity.add_component(AtomComponentProperties.material())
ground_plane_entity.set_local_uniform_scale(32.0)
ground_plane_mesh_component = ground_plane_entity.add_component(AtomComponentProperties.mesh())
ground_plane_mesh_asset_path = os.path.join("TestData", "Objects", "plane.azmodel")
ground_plane_mesh_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_mesh_asset_path, math.Uuid(), False)
ground_plane.get_set_test(1, "Controller|Configuration|Mesh Asset", ground_plane_mesh_asset_value)
# Add Atom Material component and asset.
ground_plane_mesh_asset = Asset.find_asset_by_path(ground_plane_mesh_asset_path, False)
ground_plane_mesh_component.set_component_property_value(
AtomComponentProperties.mesh('Mesh Asset'), ground_plane_mesh_asset.id)
ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial")
ground_plane_material_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False)
ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset_value)
# Create directional_light entity and set the properties
directional_light = hydra.Entity("directional_light")
directional_light.create_entity(
entity_position=math.Vector3(0.0, 0.0, 10.0),
components=["Directional Light"],
parent_id=default_level.id)
directional_light_rotation = math.Vector3(degree_radian_factor * -90.0, 0.0, 0.0)
azlmbr.components.TransformBus(
azlmbr.bus.Event, "SetLocalRotation", directional_light.id, directional_light_rotation)
# Create sphere entity and set the properties
sphere_entity = hydra.Entity("sphere")
sphere_entity.create_entity(
entity_position=math.Vector3(0.0, 0.0, 1.0),
components=["Material"],
parent_id=default_level.id)
# Work around to add the correct Atom Mesh component and asset.
sphere_entity.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", sphere_entity.id, [mesh_type_id]
).GetValue()[0]
)
ground_plane_material_asset = Asset.find_asset_by_path(ground_plane_material_asset_path, False)
ground_plane_material_component.set_component_property_value(
AtomComponentProperties.material('Material Asset'), ground_plane_material_asset.id)
# 5. "Directional Light" entity: Contains Directional Light component.
directional_light_entity = EditorEntity.create_editor_entity_at(
math.Vector3(0.0, 0.0, 10.0), AtomComponentProperties.directional_light(), default_level_entity.id)
directional_light_entity.add_component(AtomComponentProperties.directional_light())
directional_light_entity_rotation = math.Vector3(DEGREE_RADIAN_FACTOR * -90.0, 0.0, 0.0)
directional_light_entity.set_local_rotation(directional_light_entity_rotation)
# 6. "Sphere" entity: Contains Material & Mesh components.
sphere_entity = EditorEntity.create_editor_entity_at(
math.Vector3(0.0, 0.0, 1.0), "Sphere", default_level_entity.id)
sphere_mesh_component = sphere_entity.add_component(AtomComponentProperties.mesh())
sphere_mesh_asset_path = os.path.join("Models", "sphere.azmodel")
sphere_mesh_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", sphere_mesh_asset_path, math.Uuid(), False)
sphere_entity.get_set_test(1, "Controller|Configuration|Mesh Asset", sphere_mesh_asset_value)
# Add Atom Material component and asset.
sphere_mesh_asset = Asset.find_asset_by_path(sphere_mesh_asset_path, False)
sphere_mesh_component.set_component_property_value(
AtomComponentProperties.mesh('Mesh Asset'), sphere_mesh_asset.id)
sphere_material_component = sphere_entity.add_component(AtomComponentProperties.material())
sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial")
sphere_material_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False)
sphere_entity.get_set_test(0, "Default Material|Material Asset", sphere_material_asset_value)
# Create camera component and set the properties
camera_entity = hydra.Entity("camera")
camera_entity.create_entity(
entity_position=math.Vector3(5.5, -12.0, 9.0),
components=["Camera"],
parent_id=default_level.id)
rotation = math.Vector3(
degree_radian_factor * -27.0, degree_radian_factor * -12.0, degree_radian_factor * 25.0
)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", camera_entity.id, rotation)
camera_entity.get_set_test(0, "Controller|Configuration|Field of view", 60.0)
camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id)
sphere_material_asset = Asset.find_asset_by_path(sphere_material_asset_path, False)
sphere_material_component.set_component_property_value(
AtomComponentProperties.material('Material Asset'), sphere_material_asset.id)
# 7. "Camera" entity: Contains Camera component.
camera_entity = EditorEntity.create_editor_entity_at(
math.Vector3(5.5, -12.0, 9.0), AtomComponentProperties.camera(), default_level_entity.id)
camera_component = camera_entity.add_component(AtomComponentProperties.camera())
camera_entity_rotation = math.Vector3(
DEGREE_RADIAN_FACTOR * -27.0, DEGREE_RADIAN_FACTOR * -12.0, DEGREE_RADIAN_FACTOR * 25.0)
camera_entity.set_local_rotation(camera_entity_rotation)
camera_fov_value = 60.0
camera_component.set_component_property_value(AtomComponentProperties.camera('Field of view'), camera_fov_value)
azlmbr.camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id)

@ -19,6 +19,20 @@ LIGHT_TYPES = {
}
# Attenuation Radius Mode options for the Light component.
ATTENUATION_RADIUS_MODE = {
'automatic': 1,
'explicit': 0,
}
# Qualiity Level settings for Diffuse Global Illumination level component
GLOBAL_ILLUMINATION_QUALITY = {
'Low': 0,
'Medium': 1,
'High': 2,
}
class AtomComponentProperties:
"""
Holds Atom component related constants
@ -116,6 +130,21 @@ class AtomComponentProperties:
}
return properties[property]
@staticmethod
def diffuse_global_illumination(property: str = 'name') -> str:
"""
Diffuse Global Illumination level component properties.
Controls global settings for Diffuse Probe Grid components.
- 'Quality Level' from atom_constants.py GLOBAL_ILLUMINATION_QUALITY
:param property: From the last element of the property tree path. Default 'name' for component name string.
:return: Full property path OR component name if no property specified.
"""
properties = {
'name': 'Diffuse Global Illumination',
'Quality Level': 'Controller|Configuration|Quality Level'
}
return properties[property]
@staticmethod
def diffuse_probe_grid(property: str = 'name') -> str:
"""
@ -148,12 +177,17 @@ class AtomComponentProperties:
@staticmethod
def display_mapper(property: str = 'name') -> str:
"""
Display Mapper component properties.
Display Mapper level component properties.
- 'Enable LDR color grading LUT' toggles the use of LDR color grading LUT
- 'LDR color Grading LUT' is the Low Definition Range (LDR) color grading for Look-up Textures (LUT) which is
an Asset.id value corresponding to a lighting asset file.
:param property: From the last element of the property tree path. Default 'name' for component name string.
:return: Full property path OR component name if no property specified.
"""
properties = {
'name': 'Display Mapper',
'Enable LDR color grading LUT': 'Controller|Configuration|Enable LDR color grading LUT',
'LDR color Grading LUT': 'Controller|Configuration|LDR color Grading LUT',
}
return properties[property]
@ -249,13 +283,27 @@ class AtomComponentProperties:
def light(property: str = 'name') -> str:
"""
Light component properties.
- 'Attenuation Radius Mode' controls whether the attenuation radius is calculated automatically or explicitly.
- 'Color' the RGB value to set for the color of the light.
- 'Enable shadow' toggle for enabling shadows for the light.
- 'Enable shutters' toggle for enabling shutters for the light.
- 'Inner angle' inner angle value for the shutters (in degrees)
- 'Intensity' the intensity of the light in the set photometric unit (float with no ceiling).
- 'Light type' from atom_constants.py LIGHT_TYPES
- 'Outer angle' outer angle value for the shutters (in degrees)
:param property: From the last element of the property tree path. Default 'name' for component name string.
:return: Full property path OR component name if no property specified.
"""
properties = {
'name': 'Light',
'Attenuation Radius Mode': 'Controller|Configuration|Attenuation radius|Mode',
'Color': 'Controller|Configuration|Color',
'Enable shadow': 'Controller|Configuration|Shadows|Enable shadow',
'Enable shutters': 'Controller|Configuration|Shutters|Enable shutters',
'Inner angle': 'Controller|Configuration|Shutters|Inner angle',
'Intensity': 'Controller|Configuration|Intensity',
'Light type': 'Controller|Configuration|Light type',
'Outer angle': 'Controller|Configuration|Shutters|Outer angle',
}
return properties[property]
@ -390,7 +438,7 @@ class AtomComponentProperties:
'name': 'PostFX Shape Weight Modifier',
'requires': [AtomComponentProperties.postfx_layer()],
'shapes': ['Axis Aligned Box Shape', 'Box Shape', 'Capsule Shape', 'Compound Shape', 'Cylinder Shape',
'Disk Shape', 'Polygon Prism Shape', 'Quad Shape', 'Sphere Shape', 'Vegetation Reference Shape'],
'Disk Shape', 'Polygon Prism Shape', 'Quad Shape', 'Sphere Shape', 'Shape Reference'],
}
return properties[property]

@ -0,0 +1,109 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
class Tests:
creation_undo = (
"UNDO Level component addition success",
"UNDO Level component addition failed")
creation_redo = (
"REDO Level component addition success",
"REDO Level component addition failed")
diffuse_global_illumination_component = (
"Level has a Diffuse Global Illumination component",
"Level failed to find Diffuse Global Illumination component")
diffuse_global_illumination_quality = (
"Quality Level set",
"Quality Level could not be set")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
def AtomEditorComponentsLevel_DiffuseGlobalIllumination_AddedToEntity():
"""
Summary:
Tests the Diffuse Global Illumination level component can be added to the level entity and is stable.
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
Expected Behavior:
The component can be added, used in game mode, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Add Diffuse Global Illumination level component to the level entity.
2) UNDO the level component addition.
3) REDO the level component addition.
4) Set Quality Level property to Low
5) Enter/Exit game mode.
6) Look for errors and asserts.
:return: None
"""
import azlmbr.legacy.general as general
from editor_python_test_tools.editor_entity_utils import EditorLevelEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties, GLOBAL_ILLUMINATION_QUALITY
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Test steps begin.
# 1. Add Diffuse Global Illumination level component to the level entity.
diffuse_global_illumination_component = EditorLevelEntity.add_component(
AtomComponentProperties.diffuse_global_illumination())
Report.critical_result(
Tests.diffuse_global_illumination_component,
EditorLevelEntity.has_component(AtomComponentProperties.diffuse_global_illumination()))
# 2. UNDO the level component addition.
# -> UNDO component addition.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo,
not EditorLevelEntity.has_component(AtomComponentProperties.diffuse_global_illumination()))
# 3. REDO the level component addition.
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo,
EditorLevelEntity.has_component(AtomComponentProperties.diffuse_global_illumination()))
# 4. Set Quality Level property to Low
diffuse_global_illumination_component.set_component_property_value(
AtomComponentProperties.diffuse_global_illumination('Quality Level', GLOBAL_ILLUMINATION_QUALITY['Low']))
quality = diffuse_global_illumination_component.get_component_property_value(
AtomComponentProperties.diffuse_global_illumination('Quality Level'))
Report.result(diffuse_global_illumination_quality, quality == GLOBAL_ILLUMINATION_QUALITY['Low'])
# 5. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
# 6. Look for errors and asserts.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponentsLevel_DiffuseGlobalIllumination_AddedToEntity)

@ -0,0 +1,124 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
class Tests:
creation_undo = (
"UNDO level component addition success",
"UNDO level component addition failed")
creation_redo = (
"REDO Level component addition success",
"REDO Level component addition failed")
display_mapper_component = (
"Level has a Display Mapper component",
"Level failed to find Display Mapper component")
ldr_color_grading_lut = (
"LDR color Grading LUT asset set",
"LDR color Grading LUT asset could not be set")
enable_ldr_color_grading_lut = (
"Enable LDR color grading LUT set",
"Enable LDR color grading LUT could not be set")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
def AtomEditorComponentsLevel_DisplayMapper_AddedToEntity():
"""
Summary:
Tests the Display Mapper level component can be added to the level 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, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Add Display Mapper level component to the level entity.
2) UNDO the level component addition.
3) REDO the level component addition.
4) Set LDR color Grading LUT asset.
5) Set Enable LDR color grading LUT property True
6) Enter/Exit game mode.
7) Look for errors and asserts.
:return: None
"""
import os
import azlmbr.legacy.general as general
from editor_python_test_tools.asset_utils import Asset
from editor_python_test_tools.editor_entity_utils import EditorLevelEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Test steps begin.
# 1. Add Display Mapper level component to the level entity.
display_mapper_component = EditorLevelEntity.add_component(AtomComponentProperties.display_mapper())
Report.critical_result(
Tests.display_mapper_component,
EditorLevelEntity.has_component(AtomComponentProperties.display_mapper()))
# 2. UNDO the level component addition.
# -> UNDO component addition.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not EditorLevelEntity.has_component(AtomComponentProperties.display_mapper()))
# 3. REDO the level component addition.
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, EditorLevelEntity.has_component(AtomComponentProperties.display_mapper()))
# 4. Set LDR color Grading LUT asset.
display_mapper_asset_path = os.path.join("TestData", "test.lightingpreset.azasset")
display_mapper_asset = Asset.find_asset_by_path(display_mapper_asset_path, False)
display_mapper_component.set_component_property_value(
AtomComponentProperties.display_mapper('LDR color Grading LUT'), display_mapper_asset.id)
Report.result(
Tests.ldr_color_grading_lut,
display_mapper_component.get_component_property_value(
AtomComponentProperties.display_mapper('LDR color Grading LUT')) == display_mapper_asset.id)
# 5. Set Enable LDR color grading LUT property True
display_mapper_component.set_component_property_value(
AtomComponentProperties.display_mapper('Enable LDR color grading LUT'), True)
Report.result(
Test.enable_ldr_color_grading_lut,
display_mapper_component.get_component_property_value(
AtomComponentProperties.display_mapper('Enable LDR color grading LUT')) is True)
# 6. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
# 7. Look for errors and asserts.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponentsLevel_DisplayMapper_AddedToEntity)

@ -5,16 +5,8 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
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")
@ -39,6 +31,12 @@ class Tests:
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
ldr_color_grading_lut = (
"LDR color Grading LUT asset set",
"LDR color Grading LUT asset could not be set")
enable_ldr_color_grading_lut = (
"Enable LDR color grading LUT set",
"Enable LDR color grading LUT could not be set")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
@ -68,19 +66,23 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
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 and asserts.
5) Set LDR color Grading LUT asset.
6) Set Enable LDR color grading LUT property True
7) Enter/Exit game mode.
8) Test IsHidden.
9) Test IsVisible.
10) Delete Display Mapper entity.
11) UNDO deletion.
12) REDO deletion.
13) Look for errors and asserts.
:return: None
"""
import os
import azlmbr.legacy.general as general
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
from Atom.atom_utils.atom_constants import AtomComponentProperties
@ -97,7 +99,7 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
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(AtomComponentProperties.display_mapper())
display_mapper_component = display_mapper_entity.add_component(AtomComponentProperties.display_mapper())
Report.critical_result(
Tests.display_mapper_component,
display_mapper_entity.has_component(AtomComponentProperties.display_mapper()))
@ -126,33 +128,51 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, display_mapper_entity.exists())
# 5. Enter/Exit game mode.
# 5. Set LDR color Grading LUT asset.
display_mapper_asset_path = os.path.join("TestData", "test.lightingpreset.azasset")
display_mapper_asset = Asset.find_asset_by_path(display_mapper_asset_path, False)
display_mapper_component.set_component_property_value(
AtomComponentProperties.display_mapper("LDR color Grading LUT"), display_mapper_asset.id)
Report.result(
Tests.ldr_color_grading_lut,
display_mapper_component.get_component_property_value(
AtomComponentProperties.display_mapper("LDR color Grading LUT")) == display_mapper_asset.id)
# 6. Set Enable LDR color grading LUT property True
display_mapper_component.set_component_property_value(
AtomComponentProperties.display_mapper('Enable LDR color grading LUT'), True)
Report.result(
Tests.enable_ldr_color_grading_lut,
display_mapper_component.get_component_property_value(
AtomComponentProperties.display_mapper('Enable LDR color grading LUT')) is True)
# 7. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
# 8. Test IsHidden.
display_mapper_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, display_mapper_entity.is_hidden() is True)
# 7. Test IsVisible.
# 9. 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.
# 10. Delete Display Mapper entity.
display_mapper_entity.delete()
Report.result(Tests.entity_deleted, not display_mapper_entity.exists())
# 9. UNDO deletion.
# 11. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, display_mapper_entity.exists())
# 10. REDO deletion.
# 12. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not display_mapper_entity.exists())
# 11. Look for errors and asserts.
# 13. Look for errors and asserts.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")

@ -0,0 +1,199 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
class Tests:
area_light_entity_created = (
"Area Light entity successfully created",
"Area Light entity failed to be created")
area_light_entity_deleted = (
"Area Light entity was deleted",
"Area Light entity was not deleted")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
light_component_added = (
"Light component was added",
"Light component wasn't added")
light_component_attenuation_radius_property_set = (
"Light component Attenuation Radius Property was set",
"Light component Attenuation Radius Property was not set")
light_component_color_property_set = (
"Light component Color property was set",
"Light component Color property was not set")
light_component_intensity_property_set = (
"Light component Intensity property was set",
"Light component Intensity property was not set")
light_component_light_type_property_set = (
"Light type property was set",
"Light type property was not set")
def AtomGPU_LightComponent_AreaLightScreenshotsMatchGoldenImages():
"""
Summary:
Light component test using the Capsule, Spot (disk), and Point (sphere) Light type property options.
Sets each scene up and then takes a screenshot of each scene for test comparison.
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
- Close error windows and display helpers then update the viewport size.
- Runs the create_basic_atom_rendering_scene() function to setup the test scene.
Expected Behavior:
The test scripts sets up the scenes correctly and takes accurate screenshots.
Test Steps:
1. Create Area Light entity with no components.
2. Add a Light component to the Area Light entity.
3. Set the Light type property to Capsule for the Light component.
4. Set the Light component's Color property to 255, 0, 0.
5. Enter game mode and take a screenshot then exit game mode.
6. Set the Intensity property of the Light component to 0.0.
7. Set the Attenuation Radius Mode property of the Light component to 1 (automatic).
8. Enter game mode and take a screenshot then exit game mode.
9. Set the Intensity property of the Light component to 1000.0
10. Enter game mode and take a screenshot then exit game mode.
11. Set the Light type property to Spot (disk) for the Light component & rotate DEGREE_RADIAN_FACTOR * 90 degrees.
12. Enter game mode and take a screenshot then exit game mode.
13. Set the Light type property to Point (sphere) instead of Spot (disk) for the Light component.
14. Enter game mode and take a screenshot then exit game mode.
15. Delete the Area Light entity.
16. Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
import azlmbr.paths
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties, ATTENUATION_RADIUS_MODE, LIGHT_TYPES
from Atom.atom_utils.atom_component_helper import (
initial_viewport_setup, create_basic_atom_rendering_scene, enter_exit_game_mode_take_screenshot)
DEGREE_RADIAN_FACTOR = 0.0174533
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Setup: Close error windows and display helpers then update the viewport size.
TestHelper.close_error_windows()
TestHelper.close_display_helpers()
initial_viewport_setup()
general.update_viewport()
# Setup: Runs the create_basic_atom_rendering_scene() function to setup the test scene.
create_basic_atom_rendering_scene()
# Test steps begin.
# 1. Create Area Light entity with no components.
area_light_entity_name = "Area Light"
area_light_entity = EditorEntity.create_editor_entity_at(
azlmbr.math.Vector3(0.0, 0.0, 0.0), area_light_entity_name)
Report.critical_result(Tests.area_light_entity_created, area_light_entity.exists())
# 2. Add a Light component to the Area Light entity.
light_component = area_light_entity.add_component(AtomComponentProperties.light())
Report.critical_result(
Tests.light_component_added, area_light_entity.has_component(AtomComponentProperties.light()))
# 3. Set the Light type property to Capsule for the Light component.
light_component.set_component_property_value(
AtomComponentProperties.light('Light type'), LIGHT_TYPES['capsule'])
Report.result(
Tests.light_component_light_type_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Light type')) == LIGHT_TYPES['capsule'])
# 4. Set the Light component's Color property to 255, 0, 0.
light_component_color_value = azlmbr.math.Color(255.0, 0.0, 0.0, 0.0)
light_component.set_component_property_value(
AtomComponentProperties.light('Color'), light_component_color_value)
Report.result(
Tests.light_component_color_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Color')) == light_component_color_value)
# 5. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("AreaLight_1.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 6. Set the Intensity property of the Light component to 0.0.
light_component.set_component_property_value(AtomComponentProperties.light('Intensity'), 0.0)
Report.result(
Tests.light_component_intensity_property_set,
light_component.get_component_property_value(AtomComponentProperties.light('Intensity')) == 0.0)
# 7. Set the Attenuation Radius Mode property of the Light component to 1 (automatic).
light_component.set_component_property_value(
AtomComponentProperties.light('Attenuation Radius Mode'), ATTENUATION_RADIUS_MODE['automatic'])
Report.result(
Tests.light_component_attenuation_radius_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Attenuation Radius Mode')) == ATTENUATION_RADIUS_MODE['automatic'])
# 8. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("AreaLight_2.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 9. Set the Intensity property of the Light component to 1000.0
light_component.set_component_property_value(AtomComponentProperties.light('Intensity'), 1000.0)
Report.result(
Tests.light_component_intensity_property_set,
light_component.get_component_property_value(AtomComponentProperties.light('Intensity')) == 1000.0)
# 10. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("AreaLight_3.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 11. Set the Light type property to Spot (disk) for the Light component &
# rotate DEGREE_RADIAN_FACTOR * 90 degrees.
light_component.set_component_property_value(
AtomComponentProperties.light('Light type'), LIGHT_TYPES['spot_disk'])
area_light_rotation = azlmbr.math.Vector3(DEGREE_RADIAN_FACTOR * 90.0, 0.0, 0.0)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", area_light_entity.id, area_light_rotation)
Report.result(
Tests.light_component_light_type_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Light type')) == LIGHT_TYPES['spot_disk'])
# 12. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("AreaLight_4.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 13. Set the Light type property to Point (sphere) instead of Spot (disk) for the Light component.
light_component.set_component_property_value(
AtomComponentProperties.light('Light type'), LIGHT_TYPES['sphere'])
Report.result(
Tests.light_component_light_type_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Light type')) == LIGHT_TYPES['sphere'])
# 14. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("AreaLight_5.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 15. Delete the Area Light entity.
area_light_entity.delete()
Report.result(Tests.area_light_entity_deleted, not area_light_entity.exists())
# 16. Look for errors.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomGPU_LightComponent_AreaLightScreenshotsMatchGoldenImages)

@ -80,6 +80,7 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel():
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
- Deletes all existing entities before creating the scene.
Expected Behavior:
The scene can be setup for a basic level.
@ -115,7 +116,6 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel():
"""
import os
from math import isclose
import azlmbr.legacy.general as general
import azlmbr.math as math
@ -126,21 +126,11 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel():
from editor_python_test_tools.utils import Report, Tracer, TestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties
from Atom.atom_utils.atom_component_helper import initial_viewport_setup
from Atom.atom_utils.screenshot_utils import ScreenshotHelper
SCREENSHOT_NAME = "AtomBasicLevelSetup"
SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720
DEGREE_RADIAN_FACTOR = 0.0174533
def initial_viewport_setup(screen_width, screen_height):
general.set_viewport_size(screen_width, screen_height)
general.update_viewport()
TestHelper.wait_for_condition(
function=lambda: isclose(a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1)
and isclose(a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1),
timeout_in_seconds=4.0
)
SCREENSHOT_NAME = "AtomBasicLevelSetup"
with Tracer() as error_tracer:
# Test setup begins.
@ -148,11 +138,16 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel():
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Setup: Deletes all existing entities before creating the scene.
search_filter = azlmbr.entity.SearchFilter()
all_entities = azlmbr.entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter)
azlmbr.editor.ToolsApplicationRequestBus(azlmbr.bus.Broadcast, "DeleteEntities", all_entities)
# Test steps begin.
# 1. Close error windows and display helpers then update the viewport size.
TestHelper.close_error_windows()
TestHelper.close_display_helpers()
initial_viewport_setup(SCREEN_WIDTH, SCREEN_HEIGHT)
initial_viewport_setup()
general.update_viewport()
# 2. Create Default Level Entity.

@ -0,0 +1,248 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
class Tests:
directional_light_component_disabled = (
"Disabled Directional Light component",
"Couldn't disable Directional Light component")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
global_skylight_component_disabled = (
"Disabled Global Skylight (IBL) component",
"Couldn't disable Global Skylight (IBL) component")
hdri_skybox_component_disabled = (
"Disabled HDRi Skybox component",
"Couldn't disable HDRi Skybox component")
light_component_added = (
"Light component added",
"Couldn't add Light component")
light_component_color_property_set = (
"Color property was set",
"Color property was not set")
light_component_enable_shadow_property_set = (
"Enable shadow property was set",
"Enable shadow property was not set")
light_component_enable_shutters_property_set = (
"Enable shutters property was set",
"Enable shutters property was not set")
light_component_inner_angle_property_set = (
"Inner angle property was set",
"Inner angle property was not set")
light_component_intensity_property_set = (
"Intensity property was set",
"Intensity property was not set")
light_component_light_type_property_set = (
"Light type property was set",
"Light type property was not set")
light_component_outer_angle_property_set = (
"Outer angle property was set",
"Outer angle property was not set")
material_component_material_asset_property_set = (
"Material Asset property was set",
"Material Asset property was not set")
spot_light_entity_created = (
"Spot Light entity created",
"Couldn't create Spot Light entity")
def AtomGPU_LightComponent_SpotLightScreenshotsMatchGoldenImages():
"""
Summary:
Light component test using the Spot (disk) Light type property option and modifying the shadows and colors.
Sets each scene up and then takes a screenshot of each scene for test comparison.
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
- Close error windows and display helpers then update the viewport size.
- Runs the create_basic_atom_rendering_scene() function to setup the test scene.
Expected Behavior:
The test scripts sets up the scenes correctly and takes accurate screenshots.
Test Steps:
1. Find the Directional Light entity then disable its Directional Light component.
2. Disable Global Skylight (IBL) component on the Global Skylight (IBL) entity.
3. Disable HDRi Skybox component on the Global Skylight (IBL) entity.
4. Create a Spot Light entity and rotate it.
5. Attach a Light component to the Spot Light entity.
6. Set the Light component Light Type to Spot (disk).
7. Enter game mode and take a screenshot then exit game mode.
8. Change the default material asset for the Ground Plane entity.
9. Enter game mode and take a screenshot then exit game mode.
10. Increase the Intensity value of the Light component.
11. Enter game mode and take a screenshot then exit game mode.
12. Change the Light component Color property value.
13. Enter game mode and take a screenshot then exit game mode.
14. Change the Light component Enable shutters, Inner angle, and Outer angle property values.
15. Enter game mode and take a screenshot then exit game mode.
16. Change the Light component Enable shadow and Shadowmap size property values then move Spot Light entity.
17. Enter game mode and take a screenshot then exit game mode.
18. Look for errors.
:return: None
"""
import os
import azlmbr.legacy.general as general
import azlmbr.paths
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
from Atom.atom_utils.atom_constants import AtomComponentProperties, LIGHT_TYPES
from Atom.atom_utils.atom_component_helper import (
initial_viewport_setup, create_basic_atom_rendering_scene, enter_exit_game_mode_take_screenshot)
DEGREE_RADIAN_FACTOR = 0.0174533
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Setup: Close error windows and display helpers then update the viewport size.
TestHelper.close_error_windows()
TestHelper.close_display_helpers()
initial_viewport_setup()
general.update_viewport()
# Setup: Runs the create_basic_atom_rendering_scene() function to setup the test scene.
create_basic_atom_rendering_scene()
# Test steps begin.
# 1. Find the Directional Light entity then disable its Directional Light component.
directional_light_entity = EditorEntity.find_editor_entity(AtomComponentProperties.directional_light())
directional_light_component = directional_light_entity.get_components_of_type(
[AtomComponentProperties.directional_light()])[0]
directional_light_component.disable_component()
Report.critical_result(Tests.directional_light_component_disabled, not directional_light_component.is_enabled())
# 2. Disable Global Skylight (IBL) component on the Global Skylight (IBL) entity.
global_skylight_entity = EditorEntity.find_editor_entity(AtomComponentProperties.global_skylight())
global_skylight_component = global_skylight_entity.get_components_of_type(
[AtomComponentProperties.global_skylight()])[0]
global_skylight_component.disable_component()
Report.critical_result(Tests.global_skylight_component_disabled, not global_skylight_component.is_enabled())
# 3. Disable HDRi Skybox component on the Global Skylight (IBL) entity.
hdri_skybox_component = global_skylight_entity.get_components_of_type(
[AtomComponentProperties.hdri_skybox()])[0]
hdri_skybox_component.disable_component()
Report.critical_result(Tests.hdri_skybox_component_disabled, not hdri_skybox_component.is_enabled())
# 4. Create a Spot Light entity and rotate it.
spot_light_name = "Spot Light"
spot_light_entity = EditorEntity.create_editor_entity_at(
azlmbr.math.Vector3(0.7, -2.0, 1.0), spot_light_name)
rotation = azlmbr.math.Vector3(DEGREE_RADIAN_FACTOR * 300.0, 0.0, 0.0)
spot_light_entity.set_local_rotation(rotation)
Report.critical_result(Tests.spot_light_entity_created, spot_light_entity.exists())
# 5. Attach a Light component to the Spot Light entity.
light_component = spot_light_entity.add_component(AtomComponentProperties.light())
Report.critical_result(Tests.light_component_added, light_component.is_enabled())
# 6. Set the Light component Light Type to Spot (disk).
light_component.set_component_property_value(
AtomComponentProperties.light('Light type'), LIGHT_TYPES['spot_disk'])
Report.result(
Tests.light_component_light_type_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Light type')) == LIGHT_TYPES['spot_disk'])
# 7. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("SpotLight_1.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 8. Change the default material asset for the Ground Plane entity.
ground_plane_name = "Ground Plane"
ground_plane_entity = EditorEntity.find_editor_entity(ground_plane_name)
ground_plane_material_component_name = AtomComponentProperties.material()
ground_plane_material_component = ground_plane_entity.get_components_of_type(
[ground_plane_material_component_name])[0]
ground_plane_material_asset_path = os.path.join(
"Materials", "Presets", "Macbeth", "22_neutral_5-0_0-70d.azmaterial")
ground_plane_material_asset = Asset.find_asset_by_path(ground_plane_material_asset_path, False)
ground_plane_material_component.set_component_property_value(
AtomComponentProperties.material('Material Asset'), ground_plane_material_asset.id)
Report.result(
Tests.material_component_material_asset_property_set,
ground_plane_material_component.get_component_property_value(
AtomComponentProperties.material('Material Asset')) == ground_plane_material_asset.id)
# 9. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("SpotLight_2.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 10. Increase the Intensity value of the Light component.
light_component.set_component_property_value(AtomComponentProperties.light('Intensity'), 800.0)
Report.result(
Tests.light_component_intensity_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Intensity')) == 800.0)
# 11. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("SpotLight_3.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 12. Change the Light component Color property value.
color_value = azlmbr.math.Color(47.0 / 255.0, 75.0 / 255.0, 37.0 / 255.0, 255.0 / 255.0)
light_component.set_component_property_value(AtomComponentProperties.light('Color'), color_value)
Report.result(
Tests.light_component_color_property_set,
light_component.get_component_property_value(AtomComponentProperties.light('Color')) == color_value)
# 13. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("SpotLight_4.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 14. Change the Light component Enable shutters, Inner angle, and Outer angle property values.
enable_shutters = True
inner_angle = 60.0
outer_angle = 75.0
light_component.set_component_property_value(AtomComponentProperties.light('Enable shutters'), enable_shutters)
light_component.set_component_property_value(AtomComponentProperties.light('Inner angle'), inner_angle)
light_component.set_component_property_value(AtomComponentProperties.light('Outer angle'), outer_angle)
Report.result(
Tests.light_component_enable_shutters_property_set,
light_component.get_component_property_value(
AtomComponentProperties.light('Enable shutters')) == enable_shutters)
Report.result(
Tests.light_component_inner_angle_property_set,
light_component.get_component_property_value(AtomComponentProperties.light('Inner angle')) == inner_angle)
Report.result(
Tests.light_component_outer_angle_property_set,
light_component.get_component_property_value(AtomComponentProperties.light('Outer angle')) == outer_angle)
# 15. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("SpotLight_5.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 16. Change the Light component Enable shadow and slightly move Spot Light entity.
light_component.set_component_property_value(AtomComponentProperties.light('Enable shadow'), True)
Report.result(
Tests.light_component_enable_shadow_property_set,
light_component.get_component_property_value(AtomComponentProperties.light('Enable shadow')) is True)
spot_light_entity.set_world_rotation(azlmbr.math.Vector3(0.7, -2.0, 1.9))
# 17. Enter game mode and take a screenshot then exit game mode.
enter_exit_game_mode_take_screenshot("SpotLight_6.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 18. Look for errors.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomGPU_LightComponent_SpotLightScreenshotsMatchGoldenImages)

@ -1,200 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.editor_test_helper import EditorTestHelper
from Atom.atom_utils.screenshot_utils import ScreenshotHelper
SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720
DEGREE_RADIAN_FACTOR = 0.0174533
helper = EditorTestHelper(log_prefix="Test_Atom_BasicLevelSetup")
def run():
"""
1. View -> Layouts -> Restore Default Layout, sets the viewport to ratio 16:9 @ 1280 x 720
2. Runs console command r_DisplayInfo = 0
3. Deletes all entities currently present in the level.
4. Creates a "default_level" entity to hold all other entities, setting the translate values to x:0, y:0, z:0
5. Adds a Grid component to the "default_level" & updates its Grid Spacing to 1.0m
6. Adds a "global_skylight" entity to "default_level", attaching an HDRi Skybox w/ a Cubemap Texture.
7. Adds a Global Skylight (IBL) component w/ diffuse image and specular image to "global_skylight" entity.
8. Adds a "ground_plane" entity to "default_level", attaching a Mesh component & Material component.
9. Adds a "directional_light" entity to "default_level" & adds a Directional Light component.
10. Adds a "sphere" entity to "default_level" & adds a Mesh component with a Material component to it.
11. Adds a "camera" entity to "default_level" & adds a Camera component with 80 degree FOV and Transform values:
Translate - x:5.5m, y:-12.0m, z:9.0m
Rotate - x:-27.0, y:-12.0, z:25.0
12. Finally enters game mode, takes a screenshot, & exits game mode.
:return: None
"""
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.camera as camera
import azlmbr.entity as entity
import azlmbr.legacy.general as general
import azlmbr.math as math
import azlmbr.paths
import azlmbr.editor as editor
def initial_viewport_setup(screen_width, screen_height):
general.set_viewport_size(screen_width, screen_height)
general.update_viewport()
helper.wait_for_condition(
function=lambda: helper.isclose(a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1)
and helper.isclose(a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1),
timeout_in_seconds=4.0
)
result = helper.isclose(a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1) and helper.isclose(
a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1)
general.log(general.get_viewport_size().x)
general.log(general.get_viewport_size().y)
general.log(general.get_viewport_size().z)
general.log(f"Viewport is set to the expected size: {result}")
general.run_console("r_DisplayInfo = 0")
def after_level_load():
"""Function to call after creating/opening a level to ensure it loads."""
# Give everything a second to initialize.
general.idle_enable(True)
general.idle_wait(1.0)
general.update_viewport()
general.idle_wait(0.5) # half a second is more than enough for updating the viewport.
# Close out problematic windows, FPS meters, and anti-aliasing.
if general.is_helpers_shown(): # Turn off the helper gizmos if visible
general.toggle_helpers()
general.idle_wait(1.0)
if general.is_pane_visible("Error Report"): # Close Error Report windows that block focus.
general.close_pane("Error Report")
if general.is_pane_visible("Error Log"): # Close Error Log windows that block focus.
general.close_pane("Error Log")
general.idle_wait(1.0)
general.run_console("r_displayInfo=0")
general.idle_wait(1.0)
# Wait for Editor idle loop before executing Python hydra scripts.
general.idle_enable(True)
# Basic setup for opened level.
helper.open_level(level_name="Base")
after_level_load()
initial_viewport_setup(SCREEN_WIDTH, SCREEN_HEIGHT)
# Create default_level entity
search_filter = azlmbr.entity.SearchFilter()
all_entities = entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter)
editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntities", all_entities)
default_level = hydra.Entity("default_level")
position = math.Vector3(0.0, 0.0, 0.0)
default_level.create_entity(position, ["Grid"])
default_level.get_set_test(0, "Controller|Configuration|Secondary Grid Spacing", 1.0)
# Create global_skylight entity and set the properties
global_skylight = hydra.Entity("global_skylight")
global_skylight.create_entity(
entity_position=math.Vector3(0.0, 0.0, 0.0),
components=["HDRi Skybox", "Global Skylight (IBL)"],
parent_id=default_level.id
)
global_skylight_image_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage")
global_skylight_image_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", global_skylight_image_asset_path, math.Uuid(), False)
global_skylight.get_set_test(0, "Controller|Configuration|Cubemap Texture", global_skylight_image_asset)
hydra.get_set_test(global_skylight, 1, "Controller|Configuration|Diffuse Image", global_skylight_image_asset)
hydra.get_set_test(global_skylight, 1, "Controller|Configuration|Specular Image", global_skylight_image_asset)
# Create ground_plane entity and set the properties
ground_plane = hydra.Entity("ground_plane")
ground_plane.create_entity(
entity_position=math.Vector3(0.0, 0.0, 0.0),
components=["Material"],
parent_id=default_level.id
)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", ground_plane.id, 32.0)
# Work around to add the correct Atom Mesh component and asset.
mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId
ground_plane.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", ground_plane.id, [mesh_type_id]
).GetValue()[0]
)
ground_plane_mesh_asset_path = os.path.join("TestData", "Objects", "plane.azmodel")
ground_plane_mesh_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_mesh_asset_path, math.Uuid(), False)
hydra.get_set_test(ground_plane, 1, "Controller|Configuration|Mesh Asset", ground_plane_mesh_asset)
# Add Atom Material component and asset.
ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial")
ground_plane_material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False)
ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset)
# Create directional_light entity and set the properties
directional_light = hydra.Entity("directional_light")
directional_light.create_entity(
entity_position=math.Vector3(0.0, 0.0, 10.0),
components=["Directional Light"],
parent_id=default_level.id
)
rotation = math.Vector3(DEGREE_RADIAN_FACTOR * -90.0, 0.0, 0.0)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", directional_light.id, rotation)
# Create sphere entity and set the properties
sphere = hydra.Entity("sphere")
sphere.create_entity(
entity_position=math.Vector3(0.0, 0.0, 1.0),
components=["Material"],
parent_id=default_level.id
)
# Work around to add the correct Atom Mesh component and asset.
sphere.components.append(
editor.EditorComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", sphere.id, [mesh_type_id]
).GetValue()[0]
)
sphere_mesh_asset_path = os.path.join("Models", "sphere.azmodel")
sphere_mesh_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", sphere_mesh_asset_path, math.Uuid(), False)
hydra.get_set_test(sphere, 1, "Controller|Configuration|Mesh Asset", sphere_mesh_asset)
# Add Atom Material component and asset.
sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial")
sphere_material_asset = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False)
sphere.get_set_test(0, "Default Material|Material Asset", sphere_material_asset)
# Create camera component and set the properties
camera_entity = hydra.Entity("camera")
position = math.Vector3(5.5, -12.0, 9.0)
camera_entity.create_entity(components=["Camera"], entity_position=position, parent_id=default_level.id)
rotation = math.Vector3(
DEGREE_RADIAN_FACTOR * -27.0, DEGREE_RADIAN_FACTOR * -12.0, DEGREE_RADIAN_FACTOR * 25.0
)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", camera_entity.id, rotation)
camera_entity.get_set_test(0, "Controller|Configuration|Field of view", 60.0)
camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id)
# Enter game mode, take screenshot, & exit game mode.
general.idle_wait(0.5)
general.enter_game_mode()
general.idle_wait(1.0)
helper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=2.0)
ScreenshotHelper(general.idle_wait_frames).capture_screenshot_blocking(f"{'AtomBasicLevelSetup'}.ppm")
general.exit_game_mode()
helper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=2.0)
if __name__ == "__main__":
run()

@ -1,261 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import sys
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.math as math
import azlmbr.paths
import azlmbr.legacy.general as general
sys.path.append(os.path.join(azlmbr.paths.projectroot, "Gem", "PythonTests"))
import editor_python_test_tools.hydra_editor_utils as hydra
from Atom.atom_utils import atom_component_helper, atom_constants, screenshot_utils
from editor_python_test_tools.editor_test_helper import EditorTestHelper
helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper")
LEVEL_NAME = "Base"
LIGHT_COMPONENT = "Light"
LIGHT_TYPE_PROPERTY = 'Controller|Configuration|Light type'
DEGREE_RADIAN_FACTOR = 0.0174533
def run():
"""
Sets up the tests by making sure the required level is created & setup correctly.
It then executes 2 test cases - see each associated test function's docstring for more info.
Finally prints the string "Light component tests completed" after completion
Tests will fail immediately if any of these log lines are found:
1. Trace::Assert
2. Trace::Error
3. Traceback (most recent call last):
:return: None
"""
atom_component_helper.create_basic_atom_level(level_name=LEVEL_NAME)
# Run tests.
area_light_test()
spot_light_test()
general.log("Light component tests completed.")
def area_light_test():
"""
Basic test for the "Light" component attached to an "area_light" entity.
Test Case - Light Component: Capsule, Spot (disk), and Point (sphere):
1. Creates "area_light" entity w/ a Light component that has a Capsule Light type w/ the color set to 255, 0, 0
2. Enters game mode to take a screenshot for comparison, then exits game mode.
3. Sets the Light component Intensity Mode to Lumens (default).
4. Ensures the Light component Mode is Automatic (default).
5. Sets the Intensity value of the Light component to 0.0
6. Enters game mode again, takes another screenshot for comparison, then exits game mode.
7. Updates the Intensity value of the Light component to 1000.0
8. Enters game mode again, takes another screenshot for comparison, then exits game mode.
9. Swaps the Capsule light type option to Spot (disk) light type on the Light component
10. Updates "area_light" entity Transform rotate value to x: 90.0, y:0.0, z:0.0
11. Enters game mode again, takes another screenshot for comparison, then exits game mode.
12. Swaps the Spot (disk) light type for the Point (sphere) light type in the Light component.
13. Enters game mode again, takes another screenshot for comparison, then exits game mode.
14. Deletes the Light component from the "area_light" entity and verifies its successful.
"""
# Create an "area_light" entity with "Light" component using Light type of "Capsule"
area_light_entity_name = "area_light"
area_light = hydra.Entity(area_light_entity_name)
area_light.create_entity(math.Vector3(-1.0, -2.0, 3.0), [LIGHT_COMPONENT])
general.log(
f"{area_light_entity_name}_test: Component added to the entity: "
f"{hydra.has_components(area_light.id, [LIGHT_COMPONENT])}")
light_component_id_pair = hydra.attach_component_to_entity(area_light.id, LIGHT_COMPONENT)
# Select the "Capsule" light type option.
azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
light_component_id_pair,
LIGHT_TYPE_PROPERTY,
atom_constants.LIGHT_TYPES['capsule']
)
# Update color and take screenshot in game mode
color = math.Color(255.0, 0.0, 0.0, 0.0)
area_light.get_set_test(0, "Controller|Configuration|Color", color)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("AreaLight_1", area_light_entity_name)
# Update intensity value to 0.0 and take screenshot in game mode
area_light.get_set_test(0, "Controller|Configuration|Attenuation Radius|Mode", 1)
area_light.get_set_test(0, "Controller|Configuration|Intensity", 0.0)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("AreaLight_2", area_light_entity_name)
# Update intensity value to 1000.0 and take screenshot in game mode
area_light.get_set_test(0, "Controller|Configuration|Intensity", 1000.0)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("AreaLight_3", area_light_entity_name)
# Swap the "Capsule" light type option to "Spot (disk)" light type
azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
light_component_id_pair,
LIGHT_TYPE_PROPERTY,
atom_constants.LIGHT_TYPES['spot_disk']
)
area_light_rotation = math.Vector3(DEGREE_RADIAN_FACTOR * 90.0, 0.0, 0.0)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", area_light.id, area_light_rotation)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("AreaLight_4", area_light_entity_name)
# Swap the "Spot (disk)" light type to the "Point (sphere)" light type and take screenshot.
azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
light_component_id_pair,
LIGHT_TYPE_PROPERTY,
atom_constants.LIGHT_TYPES['sphere']
)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("AreaLight_5", area_light_entity_name)
editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntityById", area_light.id)
def spot_light_test():
"""
Basic test for the Light component attached to a "spot_light" entity.
Test Case - Light Component: Spot (disk) with shadows & colors:
1. Creates "spot_light" entity w/ a Light component attached to it.
2. Selects the "directional_light" entity already present in the level and disables it.
3. Selects the "global_skylight" entity already present in the level and disables the HDRi Skybox component,
as well as the Global Skylight (IBL) component.
4. Enters game mode to take a screenshot for comparison, then exits game mode.
5. Selects the "ground_plane" entity and changes updates the material to a new material.
6. Enters game mode to take a screenshot for comparison, then exits game mode.
7. Selects the "spot_light" entity and increases the Light component Intensity to 800 lm
8. Enters game mode to take a screenshot for comparison, then exits game mode.
9. Selects the "spot_light" entity and sets the Light component Color to 47, 75, 37
10. Enters game mode to take a screenshot for comparison, then exits game mode.
11. Selects the "spot_light" entity and modifies the Shutter controls to the following values:
- Enable shutters: True
- Inner Angle: 60.0
- Outer Angle: 75.0
12. Enters game mode to take a screenshot for comparison, then exits game mode.
13. Selects the "spot_light" entity and modifies the Shadow controls to the following values:
- Enable Shadow: True
- ShadowmapSize: 256
14. Modifies the world translate position of the "spot_light" entity to 0.7, -2.0, 1.9 (for casting shadows better)
15. Enters game mode to take a screenshot for comparison, then exits game mode.
"""
# Disable "Directional Light" component for the "directional_light" entity
# "directional_light" entity is created by the create_basic_atom_level() function by default.
directional_light_entity_id = hydra.find_entity_by_name("directional_light")
directional_light = hydra.Entity(name='directional_light', id=directional_light_entity_id)
directional_light_component_type = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Directional Light"], 0)[0]
directional_light_component = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'GetComponentOfType', directional_light.id, directional_light_component_type
).GetValue()
editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [directional_light_component])
general.idle_wait(0.5)
# Disable "Global Skylight (IBL)" and "HDRi Skybox" components for the "global_skylight" entity
global_skylight_entity_id = hydra.find_entity_by_name("global_skylight")
global_skylight = hydra.Entity(name='global_skylight', id=global_skylight_entity_id)
global_skylight_component_type = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Global Skylight (IBL)"], 0)[0]
global_skylight_component = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'GetComponentOfType', global_skylight.id, global_skylight_component_type
).GetValue()
editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [global_skylight_component])
hdri_skybox_component_type = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["HDRi Skybox"], 0)[0]
hdri_skybox_component = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'GetComponentOfType', global_skylight.id, hdri_skybox_component_type
).GetValue()
editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [hdri_skybox_component])
general.idle_wait(0.5)
# Create a "spot_light" entity with "Light" component using Light Type of "Spot (disk)"
spot_light_entity_name = "spot_light"
spot_light = hydra.Entity(spot_light_entity_name)
spot_light.create_entity(math.Vector3(0.7, -2.0, 1.0), [LIGHT_COMPONENT])
general.log(
f"{spot_light_entity_name}_test: Component added to the entity: "
f"{hydra.has_components(spot_light.id, [LIGHT_COMPONENT])}")
rotation = math.Vector3(DEGREE_RADIAN_FACTOR * 300.0, 0.0, 0.0)
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", spot_light.id, rotation)
light_component_type = hydra.attach_component_to_entity(spot_light.id, LIGHT_COMPONENT)
editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
light_component_type,
LIGHT_TYPE_PROPERTY,
atom_constants.LIGHT_TYPES['spot_disk']
)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("SpotLight_1", spot_light_entity_name)
# Change default material of ground plane entity and take screenshot
ground_plane_entity_id = hydra.find_entity_by_name("ground_plane")
ground_plane = hydra.Entity(name='ground_plane', id=ground_plane_entity_id)
ground_plane_asset_path = os.path.join("Materials", "Presets", "MacBeth", "22_neutral_5-0_0-70d.azmaterial")
ground_plane_asset_value = asset.AssetCatalogRequestBus(
bus.Broadcast, "GetAssetIdByPath", ground_plane_asset_path, math.Uuid(), False)
material_property_path = "Default Material|Material Asset"
material_component_type = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], 0)[0]
material_component = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'GetComponentOfType', ground_plane.id, material_component_type).GetValue()
editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
material_component,
material_property_path,
ground_plane_asset_value
)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("SpotLight_2", spot_light_entity_name)
# Increase intensity value of the Spot light and take screenshot in game mode
spot_light.get_set_test(0, "Controller|Configuration|Intensity", 800.0)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("SpotLight_3", spot_light_entity_name)
# Update the Spot light color and take screenshot in game mode
color_value = math.Color(47.0 / 255.0, 75.0 / 255.0, 37.0 / 255.0, 255.0 / 255.0)
spot_light.get_set_test(0, "Controller|Configuration|Color", color_value)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("SpotLight_4", spot_light_entity_name)
# Update the Shutter controls of the Light component and take screenshot
spot_light.get_set_test(0, "Controller|Configuration|Shutters|Enable shutters", True)
spot_light.get_set_test(0, "Controller|Configuration|Shutters|Inner angle", 60.0)
spot_light.get_set_test(0, "Controller|Configuration|Shutters|Outer angle", 75.0)
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("SpotLight_5", spot_light_entity_name)
# Update the Shadow controls, move the spot_light entity world translate position and take screenshot
spot_light.get_set_test(0, "Controller|Configuration|Shadows|Enable shadow", True)
spot_light.get_set_test(0, "Controller|Configuration|Shadows|Shadowmap size", 256.0)
azlmbr.components.TransformBus(
azlmbr.bus.Event, "SetWorldTranslation", spot_light.id, math.Vector3(0.7, -2.0, 1.9))
general.idle_wait(1.0)
screenshot_utils.take_screenshot_game_mode("SpotLight_6", spot_light_entity_name)
if __name__ == "__main__":
run()

@ -25,7 +25,7 @@ class EditorComponent:
"""
EditorComponent class used to set and get the component property value using path
EditorComponent object is returned from either of
EditorEntity.add_component() or Entity.add_components() or EditorEntity.get_component_objects()
EditorEntity.add_component() or Entity.add_components() or EditorEntity.get_components_of_type()
which also assigns self.id and self.type_id to the EditorComponent object.
"""
@ -94,6 +94,13 @@ class EditorComponent:
"""
return editor.EditorComponentAPIBus(bus.Broadcast, "IsComponentEnabled", self.id)
def disable_component(self):
"""
Used to disable the component using its id value.
:return: None
"""
editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [self.id])
@staticmethod
def get_type_ids(component_names: list) -> list:
"""
@ -107,7 +114,6 @@ class EditorComponent:
return type_ids
def convert_to_azvector3(xyz) -> azlmbr.math.Vector3:
"""
Converts a vector3-like element into a azlmbr.math.Vector3
@ -120,6 +126,7 @@ def convert_to_azvector3(xyz) -> azlmbr.math.Vector3:
else:
raise ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3")
class EditorEntity:
"""
Entity class is used to create and interact with Editor Entities.
@ -136,10 +143,11 @@ class EditorEntity:
# Creation functions
@classmethod
def find_editor_entity(cls, entity_name: str, must_be_unique : bool = False) -> EditorEntity:
def find_editor_entity(cls, entity_name: str, must_be_unique: bool = False) -> EditorEntity:
"""
Given Entity name, outputs entity object
:param entity_name: Name of entity to find
:param must_be_unique: bool that asserts the entity_name specified is unique when set to True
:return: EditorEntity class object
"""
entities = cls.find_editor_entities([entity_name])
@ -147,14 +155,14 @@ class EditorEntity:
if must_be_unique:
assert len(entities) == 1, f"Failure: Multiple entities with name: '{entity_name}' when expected only one"
entity = cls(entities[0])
entity = entities[0]
return entity
@classmethod
def find_editor_entities(cls, entity_names: List[str]) -> EditorEntity:
def find_editor_entities(cls, entity_names: List[str]) -> List[EditorEntity]:
"""
Given Entities names, returns a list of EditorEntity
:param entity_name: Name of entity to find
:param entity_names: List of entity names to find
:return: List[EditorEntity] class object
"""
searchFilter = azlmbr.entity.SearchFilter()
@ -438,7 +446,7 @@ class EditorEntity:
def set_local_rotation(self, new_rotation) -> None:
"""
Sets the set the local rotation(relative to the parent) of the current entity.
:param vector3_rotation: The math.Vector3 value to use for rotation on the entity (uses radians).
:param new_rotation: The math.Vector3 value to use for rotation on the entity (uses radians).
:return: None
"""
new_rotation = convert_to_azvector3(new_rotation)
@ -454,7 +462,7 @@ class EditorEntity:
def set_local_translation(self, new_translation) -> None:
"""
Sets the local translation(relative to the parent) of the current entity.
:param vector3_translation: The math.Vector3 value to use for translation on the entity.
:param new_translation: The math.Vector3 value to use for translation on the entity.
:return: None
"""
new_translation = convert_to_azvector3(new_translation)
@ -470,3 +478,99 @@ class EditorEntity:
assert self.id.isValid(), "A valid entity id is required to focus on its owning prefab."
focus_prefab_result = azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", self.id)
assert focus_prefab_result.IsSuccess(), f"Prefab operation 'FocusOnOwningPrefab' failed. Error: {focus_prefab_result.GetError()}"
class EditorLevelEntity:
"""
EditorLevel class used to add and fetch level components.
Level entity is a special entity that you do not create/destroy independently of larger systems of level creation.
This collects a number of staticmethods that do not rely on entityId since Level entity is found internally by
EditorLevelComponentAPIBus requests.
"""
@staticmethod
def get_type_ids(component_names: list) -> list:
"""
Used to get type ids of given components list for EntityType Level
:param: component_names: List of components to get type ids
:return: List of type ids of given components.
"""
type_ids = editor.EditorComponentAPIBus(
bus.Broadcast, "FindComponentTypeIdsByEntityType", component_names, azlmbr.entity.EntityType().Level
)
return type_ids
@staticmethod
def add_component(component_name: str) -> EditorComponent:
"""
Used to add new component to Level.
:param component_name: String of component name to add.
:return: Component object of newly added component.
"""
component = EditorLevelEntity.add_components([component_name])[0]
return component
@staticmethod
def add_components(component_names: list) -> List[EditorComponent]:
"""
Used to add multiple components
:param: component_names: List of components to add to level
:return: List of newly added components to the level
"""
components = []
type_ids = EditorLevelEntity.get_type_ids(component_names)
for type_id in type_ids:
new_comp = EditorComponent()
new_comp.type_id = type_id
add_component_outcome = editor.EditorLevelComponentAPIBus(
bus.Broadcast, "AddComponentsOfType", [type_id]
)
assert (
add_component_outcome.IsSuccess()
), f"Failure: Could not add component: '{new_comp.get_component_name()}' to level"
new_comp.id = add_component_outcome.GetValue()[0]
components.append(new_comp)
return components
@staticmethod
def get_components_of_type(component_names: list) -> List[EditorComponent]:
"""
Used to get components of type component_name that already exists on the level
:param component_names: List of names of components to check
:return: List of Level Component objects of given component name
"""
component_list = []
type_ids = EditorLevelEntity.get_type_ids(component_names)
for type_id in type_ids:
component = EditorComponent()
component.type_id = type_id
get_component_of_type_outcome = editor.EditorLevelComponentAPIBus(
bus.Broadcast, "GetComponentOfType", type_id
)
assert (
get_component_of_type_outcome.IsSuccess()
), f"Failure: Level does not have component:'{component.get_component_name()}'"
component.id = get_component_of_type_outcome.GetValue()
component_list.append(component)
return component_list
@staticmethod
def has_component(component_name: str) -> bool:
"""
Used to verify if the level has the specified component
:param component_name: Name of component to check for
:return: True, if level has specified component. Else, False
"""
type_ids = EditorLevelEntity.get_type_ids([component_name])
return editor.EditorLevelComponentAPIBus(bus.Broadcast, "HasComponentOfType", type_ids[0])
@staticmethod
def count_components_of_type(component_name: str) -> int:
"""
Used to get a count of the specified level component attached to the level
:param component_name: Name of component to check for
:return: integer count of occurences of level component attached to level or zero if none are present
"""
type_ids = EditorLevelEntity.get_type_ids([component_name])
return editor.EditorLevelComponentAPIBus(bus.Broadcast, "CountComponentsOfType", type_ids[0])

@ -34,6 +34,38 @@ class TestHelper:
# JIRA: SPEC-2880
# general.idle_wait_frames(1)
@staticmethod
def create_level(level_name: str) -> bool:
"""
:param level_name: The name of the level to be created
:return: True if ECreateLevelResult returns 0, False otherwise with logging to report reason
"""
Report.info(f"Creating level {level_name}")
# Use these hardcoded values to pass expected values for old terrain system until new create_level API is
# available
heightmap_resolution = 1024
heightmap_meters_per_pixel = 1
terrain_texture_resolution = 4096
use_terrain = False
result = general.create_level_no_prompt(level_name, heightmap_resolution, heightmap_meters_per_pixel,
terrain_texture_resolution, use_terrain)
# Result codes are ECreateLevelResult defined in CryEdit.h
if result == 1:
Report.info(f"{level_name} level already exists")
elif result == 2:
Report.info("Failed to create directory")
elif result == 3:
Report.info("Directory length is too long")
elif result != 0:
Report.info("Unknown error, failed to create level")
else:
Report.info(f"{level_name} level created successfully")
return result == 0
@staticmethod
def open_level(directory : str, level : str):
# type: (str, str) -> None
@ -56,8 +88,7 @@ class TestHelper:
general.idle_wait_frames(200)
@staticmethod
def enter_game_mode(msgtuple_success_fail : Tuple[str, str]):
# type: (tuple) -> None
def enter_game_mode(msgtuple_success_fail: Tuple[str, str]) -> None:
"""
:param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode.
@ -70,8 +101,7 @@ class TestHelper:
Report.critical_result(msgtuple_success_fail, general.is_in_game_mode())
@staticmethod
def multiplayer_enter_game_mode(msgtuple_success_fail : Tuple[str, str], sv_default_player_spawn_asset : str):
# type: (tuple) -> None
def multiplayer_enter_game_mode(msgtuple_success_fail: Tuple[str, str], sv_default_player_spawn_asset: str) -> None:
"""
:param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode.
:param sv_default_player_spawn_asset: The path to the network player prefab that will be automatically spawned upon entering gamemode. The engine default is "prefabs/player.network.spawnable"

@ -98,38 +98,32 @@ class FileManagement:
"""
file_map = FileManagement._load_file_map()
backup_path = FileManagement.backup_folder_path
backup_file_name = "{}.bak".format(file_name)
backup_file = os.path.join(backup_path, backup_file_name)
# If backup directory DNE, make one
if not os.path.exists(backup_path):
os.mkdir(backup_path)
# If "traditional" backup file exists, delete it (myFile.txt.bak)
if os.path.exists(backup_file):
fs.delete([backup_file], True, False)
# Find my next storage name (myFile_1.txt.bak)
backup_storage_file_name = FileManagement._next_available_name(backup_file_name, file_map)
if backup_storage_file_name is None:
# Find my next storage name (myFile_1.txt)
backup_file_name = FileManagement._next_available_name(file_name, file_map)
if backup_file_name is None:
# If _next_available_name returns None, we have backed up MAX_BACKUPS of files name [file_name]
raise Exception(
"FileManagement class ran out of backups per name. Max: {}".format(FileManagement.MAX_BACKUPS)
)
backup_storage_file = os.path.join(backup_path, backup_storage_file_name)
# If this backup file already exists, delete it.
backup_storage_file = "{}.bak".format(os.path.normpath(os.path.join(backup_path, backup_file_name)))
if os.path.exists(backup_storage_file):
# This file should not exists, but if it does it's about to get clobbered!
fs.unlock_file(backup_storage_file)
# Create "traditional" backup file (myFile.txt.bak)
fs.create_backup(os.path.join(file_path, file_name), backup_path)
# Copy "traditional" backup file into storage backup (myFile_1.txt.bak)
FileManagement._copy_file(backup_file_name, backup_path, backup_storage_file_name, backup_path)
fs.lock_file(backup_storage_file)
# Delete "traditional" back up file
fs.unlock_file(backup_file)
fs.delete([backup_file], True, False)
fs.delete([backup_storage_file], True, False)
# Create backup file (myFile_1.txt.bak)
original_file = os.path.normpath(os.path.join(file_path, file_name))
fs.create_backup(original_file, backup_path, backup_file_name)
# Update file map with new file
file_map[os.path.join(file_path, file_name)] = backup_storage_file_name
file_map[original_file] = backup_file_name
FileManagement._save_file_map(file_map)
# Unlock original file to get it ready to be edited by the test
fs.unlock_file(os.path.join(file_path, file_name))
fs.unlock_file(original_file)
@staticmethod
def _restore_file(file_name, file_path):
@ -143,20 +137,15 @@ class FileManagement:
"""
file_map = FileManagement._load_file_map()
backup_path = FileManagement.backup_folder_path
src_file = os.path.join(file_path, file_name)
src_file = os.path.normpath(os.path.join(file_path, file_name))
if src_file in file_map:
backup_file = os.path.join(backup_path, file_map[src_file])
if os.path.exists(backup_file):
fs.unlock_file(backup_file)
backup_file_name = file_map[src_file]
backup_file = "{}.bak".format(os.path.join(backup_path, backup_file_name))
fs.unlock_file(src_file)
# Make temporary copy of backed up file to restore from
temp_file = "{}.bak".format(file_name)
FileManagement._copy_file(file_map[src_file], backup_path, temp_file, backup_path)
fs.restore_backup(src_file, backup_path)
fs.lock_file(src_file)
# Delete backup file
fs.delete([os.path.join(backup_path, temp_file)], True, False)
if fs.restore_backup(src_file, backup_path, backup_file_name):
fs.delete([backup_file], True, False)
# Remove from file map
del file_map[src_file]
FileManagement._save_file_map(file_map)

@ -12,7 +12,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
NAME AutomatedTesting::PrefabTests
TEST_SUITE main
TEST_SERIAL
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_Optimized.py
RUNTIME_DEPENDENCIES
Legacy::Editor
AZ::AssetProcessor

@ -0,0 +1,52 @@
"""
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 EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
@pytest.mark.SUITE_main
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomationNoAutoTestMode(EditorTestSuite):
# Enable only -BatchMode for these tests. Some tests cannot run in -autotest_mode due to UI interactions
global_extra_cmdline_args = ["-BatchMode"]
class test_CreatePrefab_UnderAnEntity(EditorSharedTest):
from .tests.create_prefab import CreatePrefab_UnderAnEntity as test_module
class test_CreatePrefab_UnderAnotherPrefab(EditorSharedTest):
from .tests.create_prefab import CreatePrefab_UnderAnotherPrefab as test_module
class test_DeleteEntity_UnderAnotherPrefab(EditorSharedTest):
from .tests.delete_entity import DeleteEntity_UnderAnotherPrefab as test_module
class test_DeleteEntity_UnderLevelPrefab(EditorSharedTest):
from .tests.delete_entity import DeleteEntity_UnderLevelPrefab as test_module
class test_ReparentPrefab_UnderAnotherPrefab(EditorSharedTest):
from .tests.reparent_prefab import ReparentPrefab_UnderAnotherPrefab as test_module
class test_DetachPrefab_UnderAnotherPrefab(EditorSharedTest):
from .tests.detach_prefab import DetachPrefab_UnderAnotherPrefab as test_module
class test_OpenLevel_ContainingTwoEntities(EditorSharedTest):
from .tests.open_level import OpenLevel_ContainingTwoEntities as test_module
class test_CreatePrefab_WithSingleEntity(EditorSharedTest):
from .tests.create_prefab import CreatePrefab_WithSingleEntity as test_module
class test_InstantiatePrefab_ContainingASingleEntity(EditorSharedTest):
from .tests.instantiate_prefab import InstantiatePrefab_ContainingASingleEntity as test_module
class test_DeletePrefab_ContainingASingleEntity(EditorSharedTest):
from .tests.delete_prefab import DeletePrefab_ContainingASingleEntity as test_module
class test_DuplicatePrefab_ContainingASingleEntity(EditorSharedTest):
from .tests.duplicate_prefab import DuplicatePrefab_ContainingASingleEntity as test_module

@ -24,6 +24,7 @@ def CreatePrefab_WithSingleEntity():
# Creates a prefab from the new entity
Prefab.create_prefab(car_prefab_entities, CAR_PREFAB_FILE_NAME)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(CreatePrefab_WithSingleEntity)

@ -7,8 +7,8 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
def DetachPrefab_UnderAnotherPrefab():
CAR_PREFAB_FILE_NAME = 'car_prefab'
WHEEL_PREFAB_FILE_NAME = 'wheel_prefab'
CAR_PREFAB_FILE_NAME = 'car_prefab2'
WHEEL_PREFAB_FILE_NAME = 'wheel_prefab2'
import editor_python_test_tools.pyside_utils as pyside_utils

@ -0,0 +1,144 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
class HeightTests:
single_gradient_height_correct = (
"Successfully retrieved height for gradient1.",
"Failed to retrieve height for gradient1."
)
double_gradient_height_correct = (
"Successfully retrieved height when two gradients exist.",
"Failed to retrieve height when two gradients exist."
)
triple_gradient_height_correct = (
"Successfully retrieved height when three gradients exist.",
"Failed to retrieve height when three gradients exist."
)
terrain_data_changed_call_count_correct = (
"OnTerrainDataChanged called expected number of times.",
"OnTerrainDataChanged call count incorrect."
)
def TerrainHeightGradientList_AddRemoveGradientWorks():
"""
Summary:
Test aspects of the TerrainHeightGradientList through the BehaviorContext and the Property Tree.
:return: None
"""
import os
import math as sys_math
import azlmbr.legacy.general as general
import azlmbr.bus as bus
import azlmbr.math as math
import azlmbr.terrain as terrain
import azlmbr.editor as editor
import azlmbr.vegetation as vegetation
import azlmbr.entity as EntityId
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
import editor_python_test_tools.pyside_utils as pyside_utils
from editor_python_test_tools.editor_entity_utils import EditorEntity
terrain_changed_call_count = 0
expected_terrain_changed_calls = 0
aabb_component_name = "Axis Aligned Box Shape"
gradientlist_component_name = "Terrain Height Gradient List"
layerspawner_component_name = "Terrain Layer Spawner"
gradient_value_path = "Configuration|Value"
def create_entity_at(entity_name, components_to_add, x, y, z):
entity = hydra.Entity(entity_name)
entity.create_entity(math.Vector3(x, y, z), components_to_add)
return entity
def on_terrain_changed(args):
nonlocal terrain_changed_call_count
terrain_changed_call_count += 1
def set_component_path_val(entity, component, path, value):
entity.get_set_test(component, path, value)
def set_gradients_check_height(main_entity, gradient_list, expected_height, test_results):
nonlocal expected_terrain_changed_calls
test_tolerance = 0.01
gradient_list_path = "Configuration|Gradient Entities"
set_component_path_val(main_entity, 1, gradient_list_path, gradient_list)
expected_terrain_changed_calls += 1
# Wait until the terrain data has been updated.
helper.wait_for_condition(lambda: terrain_changed_call_count == expected_terrain_changed_calls, 2.0)
# Get the height at the origin.
height = terrain.TerrainDataRequestBus(bus.Broadcast, "GetHeightFromFloats", 0.0, 0.0, 0)
Report.result(test_results, sys_math.isclose(height, expected_height, abs_tol=test_tolerance))
helper.init_idle()
# Open a level.
helper.open_level("Physics", "Base")
helper.wait_for_condition(lambda: general.get_current_level_name() == "Base", 2.0)
general.idle_wait_frames(1)
# Add a terrain world component
world_component = hydra.add_level_component("Terrain World")
aabb_height = 1024.0
box_dimensions = math.Vector3(1.0, 1.0, aabb_height);
# Create a main entity with a LayerSpawner, AAbb and HeightGradientList.
main_entity = create_entity_at("entity2", [layerspawner_component_name, gradientlist_component_name, aabb_component_name], 0.0, 0.0, aabb_height/2.0)
# Create three gradient entities.
gradient_entity1 = create_entity_at("Constant Gradient1", ["Constant Gradient"], 0.0, 0.0, 0.0);
gradient_entity2 = create_entity_at("Constant Gradient2", ["Constant Gradient"], 0.0, 0.0, 0.0);
gradient_entity3 = create_entity_at("Constant Gradient3", ["Constant Gradient"], 0.0, 0.0, 0.0);
# Give everything a chance to finish initializing.
general.idle_wait_frames(1)
# Set the gradients to different values.
gradient_values = [0.5, 0.8, 0.3]
set_component_path_val(gradient_entity1, 0, gradient_value_path, gradient_values[0])
set_component_path_val(gradient_entity2, 0, gradient_value_path, gradient_values[1])
set_component_path_val(gradient_entity3, 0, gradient_value_path, gradient_values[2])
# Give the TerrainSystem time to tick.
general.idle_wait_frames(1)
# Set the dimensions of the Aabb.
set_component_path_val(main_entity, 2, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
# Set up a handler to wait for notifications from the TerrainSystem.
handler = azlmbr.terrain.TerrainDataNotificationBusHandler()
handler.connect()
handler.add_callback("OnTerrainDataChanged", on_terrain_changed)
# Add a gradient to GradientList, then check the height returned from the TerrainSystem is correct.
set_gradients_check_height(main_entity, [gradient_entity1.id], aabb_height * gradient_values[0], HeightTests.single_gradient_height_correct)
# Add gradient2 and check height at the origin, this should have changed to match the second gradient value.
set_gradients_check_height(main_entity, [gradient_entity1.id, gradient_entity2.id], aabb_height * gradient_values[1], HeightTests.double_gradient_height_correct)
# Add gradient3, the height should still be the second value, as that was the highest.
set_gradients_check_height(main_entity, [gradient_entity1.id, gradient_entity2.id, gradient_entity3.id], aabb_height * gradient_values[1], HeightTests.triple_gradient_height_correct)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(TerrainHeightGradientList_AddRemoveGradientWorks)

@ -0,0 +1,166 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
class MacroMaterialTests:
setup_test = (
"Setup successful",
"Setup failed"
)
material_changed_not_called_when_inactive = (
"OnTerrainMacroMaterialRegionChanged not called successfully",
"OnTerrainMacroMaterialRegionChanged called when component inactive."
)
material_created = (
"MaterialCreated called successfully",
"MaterialCreated failed"
)
material_destroyed = (
"MaterialDestroyed called successfully",
"MaterialDestroyed failed"
)
material_recreated = (
"MaterialCreated called successfully on second test",
"MaterialCreated failed on second test"
)
material_changed_call_on_aabb_change = (
"OnTerrainMacroMaterialRegionChanged called successfully",
"Timed out waiting for OnTerrainMacroMaterialRegionChanged"
)
def TerrainMacroMaterialComponent_MacroMaterialActivates():
"""
Summary:
Load an empty level, create a MacroMaterialComponent and check assigning textures results in the correct callbacks.
:return: None
"""
import os
import math as sys_math
import azlmbr.legacy.general as general
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.math as math
import azlmbr.terrain as terrain
import azlmbr.editor as editor
import azlmbr.vegetation as vegetation
import azlmbr.entity as EntityId
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
import editor_python_test_tools.pyside_utils as pyside_utils
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.asset_utils import Asset
material_created_called = False
material_changed_called = False
material_region_changed_called = False
material_destroyed_called = False
def create_entity_at(entity_name, components_to_add, x, y, z):
entity = EditorEntity.create_editor_entity_at([x, y, z], entity_name)
for component in components_to_add:
entity.add_component(component)
return entity
def on_macro_material_created(args):
nonlocal material_created_called
material_created_called = True
def on_macro_material_changed(args):
nonlocal material_changed_called
material_changed_called = True
def on_macro_material_region_changed(args):
nonlocal material_region_changed_called
material_region_changed_called = True
def on_macro_material_destroyed(args):
nonlocal material_destroyed_called
material_destroyed_called = True
helper.init_idle()
# Open a level.
helper.open_level("Physics", "Base")
helper.wait_for_condition(lambda: general.get_current_level_name() == "Base", 2.0)
general.idle_wait_frames(1)
# Set up a handler to wait for notifications from the TerrainSystem.
handler = terrain.TerrainMacroMaterialAutomationBusHandler()
handler.connect()
handler.add_callback("OnTerrainMacroMaterialCreated", on_macro_material_created)
handler.add_callback("OnTerrainMacroMaterialChanged", on_macro_material_changed)
handler.add_callback("OnTerrainMacroMaterialRegionChanged", on_macro_material_region_changed)
handler.add_callback("OnTerrainMacroMaterialDestroyed", on_macro_material_destroyed)
macro_material_entity = create_entity_at("macro", ["Terrain Macro Material", "Axis Aligned Box Shape"], 0.0, 0.0, 0.0)
# Check that no macro material callbacks happened. It should be "inactive" as it has no assets assigned.
setup_success = not material_created_called and not material_changed_called and not material_region_changed_called and not material_destroyed_called
Report.result(MacroMaterialTests.setup_test, setup_success)
# Find the aabb component.
aabb_component_type_id_type = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Axis Aligned Box Shape"], 0)[0]
aabb_component_id = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'GetComponentOfType', macro_material_entity.id, aabb_component_type_id_type).GetValue()
# Change the aabb dimensions
material_region_changed_called = False
box_dimensions_path = "Axis Aligned Box Shape|Box Configuration|Dimensions"
editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", aabb_component_id, box_dimensions_path, math.Vector3(1.0, 1.0, 1.0))
# Check we don't receive a callback. The macro material component should be inactive as it has no images assigned.
general.idle_wait_frames(1)
Report.result(MacroMaterialTests.material_changed_not_called_when_inactive, material_region_changed_called == False)
# Find the macro material component.
macro_material_id_type = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Terrain Macro Material"], 0)[0]
macro_material_component_id = azlmbr.editor.EditorComponentAPIBus(azlmbr.bus.Broadcast, 'GetComponentOfType', macro_material_entity.id, macro_material_id_type).GetValue()
# Find a color image asset.
color_image_path = os.path.join("assets", "textures", "image.png.streamingimage")
color_image_asset = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", color_image_path, math.Uuid(), False)
# Assign the image to the MacroMaterial component, which should result in a created message.
material_created_called = False
color_texture_path = "Configuration|Color Texture"
editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", macro_material_component_id, color_texture_path, color_image_asset)
call_result = helper.wait_for_condition(lambda: material_created_called == True, 2.0)
Report.result(MacroMaterialTests.material_created, call_result)
# Find a normal image asset.
normal_image_path = os.path.join("assets", "textures", "normal.png.streamingimage")
normal_image_asset = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", normal_image_path, math.Uuid(), False)
# Assign the normal image to the MacroMaterial component, which should result in a created message.
material_created_called = False
material_destroyed_called = False
normal_texture_path = "Configuration|Normal Texture"
editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", macro_material_component_id, normal_texture_path, normal_image_asset)
# Check the MacroMaterial was destroyed and recreated.
destroyed_call_result = helper.wait_for_condition(lambda: material_destroyed_called == True, 2.0)
Report.result(MacroMaterialTests.material_destroyed, destroyed_call_result)
recreated_call_result = helper.wait_for_condition(lambda: material_created_called == True, 2.0)
Report.result(MacroMaterialTests.material_recreated, recreated_call_result)
# Change the aabb dimensions.
box_dimensions_path = "Axis Aligned Box Shape|Box Configuration|Dimensions"
editor.EditorComponentAPIBus(bus.Broadcast, "SetComponentProperty", aabb_component_id, box_dimensions_path, math.Vector3(1.0, 1.0, 1.0))
# Check that a callback is received.
region_changed_call_result = helper.wait_for_condition(lambda: material_region_changed_called == True, 2.0)
Report.result(MacroMaterialTests.material_changed_call_on_aabb_change, region_changed_call_result)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(TerrainMacroMaterialComponent_MacroMaterialActivates)

@ -0,0 +1,214 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
class VegetationTests:
vegetation_on_gradient_1 = (
"Vegetation detected at correct position on Gradient1",
"Vegetation not detected at correct position on Gradient1"
)
vegetation_on_gradient_2 = (
"Vegetation detected at correct position on Gradient2",
"Vegetation not detected at correct position on Gradient2"
)
unfiltered_vegetation_count_correct = (
"Unfiltered vegetation spawn count correct",
"Unfiltered vegetation spawn count incorrect"
)
testTag2_excluded_vegetation_count_correct = (
"TestTag2 filtered vegetation count correct",
"TestTag2 filtered vegetation count incorrect"
)
testTag2_excluded_vegetation_z_correct = (
"TestTag2 filtered vegetation spawned in correct position",
"TestTag2 filtered vegetation failed to spawn in correct position"
)
testTag3_excluded_vegetation_count_correct = (
"TestTag3 filtered vegetation count correct",
"TestTag3 filtered vegetation count incorrect"
)
testTag3_excluded_vegetation_z_correct = (
"TestTag3 filtered vegetation spawned in correct position",
"TestTag3 filtered vegetation failed to spawn in correct position"
)
cleared_exclusion_vegetation_count_correct = (
"Cleared filter vegetation count correct",
"Cleared filter vegetation count incorrect"
)
def TerrainSystem_VegetationSpawnsOnTerrainSurfaces():
"""
Summary:
Load an empty level,
Create two entities with constant gradient components with different values.
Create two entities with TerrainLayerSpawners
Create an entity to spawn vegetation
Ensure that vegetation spawns at the correct heights
Add a VegetationSurfaceMaskFilter and ensure it responds correctly to surface changes.
:return: None
"""
import os
import sys
import math as sys_math
import azlmbr.legacy.general as general
import azlmbr.bus as bus
import azlmbr.math as math
import azlmbr.areasystem as areasystem
import azlmbr.editor as editor
import azlmbr.vegetation as vegetation
import azlmbr.terrain as terrain
import azlmbr.entity as EntityId
import azlmbr.surface_data as surface_data
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
def create_entity_at(entity_name, components_to_add, x, y, z):
entity = hydra.Entity(entity_name)
entity.create_entity(math.Vector3(x, y, z), components_to_add)
return entity
def FindHighestAndLowestZValuesInArea(aabb):
vegetation_items = areasystem.AreaSystemRequestBus(bus.Broadcast, 'GetInstancesInAabb', aabb)
lowest_z = min([item.position.z for item in vegetation_items])
highest_z = max([item.position.z for item in vegetation_items])
return highest_z, lowest_z
helper.init_idle()
# Open an empty level.
helper.open_level("Physics", "Base")
helper.wait_for_condition(lambda: general.get_current_level_name() == "Base", 2.0)
general.idle_wait_frames(1)
box_height = 20.0
box_y_position = 10.0
box_dimensions = math.Vector3(20.0, 20.0, box_height)
# Add Terrain Rendering
hydra.add_level_component("Terrain World")
hydra.add_level_component("Terrain World Renderer")
# Create two terrain entities at adjoining positions
terrain_entity_1 = create_entity_at("Terrain1", ["Terrain Layer Spawner", "Axis Aligned Box Shape", "Terrain Height Gradient List", "Terrain Surface Gradient List"], 0.0, box_y_position, box_height/2.0)
terrain_entity_1.get_set_test(1, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
terrain_entity_2 = create_entity_at("Terrain2", ["Terrain Layer Spawner", "Axis Aligned Box Shape", "Terrain Height Gradient List", "Terrain Surface Gradient List"], 20.0, box_y_position, box_height/2.0)
terrain_entity_2.get_set_test(1, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
# Create two gradient entities.
gradient_value_1 = 0.25
gradient_value_2 = 0.5
gradient_entity_1 = create_entity_at("Gradient1", ["Constant Gradient"], 0.0, 0.0, 0.0)
gradient_entity_1.get_set_test(0, "Configuration|Value", gradient_value_1)
gradient_entity_2 = create_entity_at("Gradient2", ["Constant Gradient"], 0.0, 0.0, 0.0)
gradient_entity_2.get_set_test(0, "Configuration|Value", gradient_value_2)
mapping = terrain.TerrainSurfaceGradientMapping()
mapping.gradientEntityId = gradient_entity_1.id
pte = hydra.get_property_tree(terrain_entity_1.components[3])
pte.add_container_item("Configuration|Gradient to Surface Mappings", 0, mapping)
mapping = terrain.TerrainSurfaceGradientMapping()
mapping.gradientEntityId = gradient_entity_2.id
pte = hydra.get_property_tree(terrain_entity_2.components[3])
pte.add_container_item("Configuration|Gradient to Surface Mappings", 0, mapping)
# create a vegetation entity that overlaps both terrain entities.
vegetation_entity = create_entity_at("Vegetation", ["Vegetation Layer Spawner", "Axis Aligned Box Shape", "Vegetation Asset List", "Vegetation Surface Mask Filter"], 10.0, box_y_position, box_height/2.0)
vegetation_entity.get_set_test(1, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions)
# Set the vegetation area to a PrefabInstanceSpawner with a specific prefab asset selected.
prefab_spawner = vegetation.PrefabInstanceSpawner()
prefab_spawner.SetPrefabAssetPath(os.path.join("Prefabs", "PinkFlower.spawnable"))
descriptor = hydra.get_component_property_value(vegetation_entity.components[2], 'Configuration|Embedded Assets|[0]')
descriptor.spawner = prefab_spawner
vegetation_entity.get_set_test(2, "Configuration|Embedded Assets|[0]", descriptor)
# Assign gradients to layer spawners.
terrain_entity_1.get_set_test(2, "Configuration|Gradient Entities", [gradient_entity_1.id])
terrain_entity_2.get_set_test(2, "Configuration|Gradient Entities", [gradient_entity_2.id])
# Move view so that the entities are visible.
general.set_current_view_position(17.0, -66.0, 41.0)
general.set_current_view_rotation(-15, 0, 0)
# Expected item counts under conditions to be tested.
# By default, vegetation spawns at a density of 20 items per 16 meters,
# so in a 20m square, there should be around 25 ^ 2 items depending on whether area edges are included.
# In this case there are 26 ^ 2 items.
expected_surface_tag_excluded_item_count = 338
expected_no_exclusions_item_count = 676
# Wait for the vegetation to spawn
helper.wait_for_condition(lambda: vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id) == expected_no_exclusions_item_count, 5.0)
# Check the spawn count is correct.
item_count = vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id)
Report.result(VegetationTests.unfiltered_vegetation_count_correct, item_count == expected_no_exclusions_item_count)
test_aabb = math.Aabb_CreateFromMinMax(math.Vector3(-10.0, -10.0, 0.0), math.Vector3(30.0, 10.0, box_height))
# Find the z positions of the items with the lowest and highest x values, this will avoid the overlap area where z values are blended between the surface heights.
highest_z, lowest_z = FindHighestAndLowestZValuesInArea(test_aabb)
# Check that the z values are as expected.
Report.result(VegetationTests.vegetation_on_gradient_1, sys_math.isclose(lowest_z, box_height * gradient_value_1, abs_tol=0.01))
Report.result(VegetationTests.vegetation_on_gradient_2, sys_math.isclose(highest_z, box_height * gradient_value_2, abs_tol=0.01))
# Assign SurfaceTags to the SurfaceGradientLists
terrain_entity_1.get_set_test(3, "Configuration|Gradient to Surface Mappings|[0]|Surface Tag", surface_data.SurfaceTag("test_tag2"))
terrain_entity_2.get_set_test(3, "Configuration|Gradient to Surface Mappings|[0]|Surface Tag", surface_data.SurfaceTag("test_tag3"))
# Give the VegetationSurfaceFilter an exclusion list, set it to exclude test_tag2 which should remove all the lower items which are in terrain_entity_1.
vegetation_entity.get_set_test(3, "Configuration|Exclusion|Surface Tags", [surface_data.SurfaceTag()])
vegetation_entity.get_set_test(3, "Configuration|Exclusion|Surface Tags|[0]", surface_data.SurfaceTag("test_tag2"))
# Wait for the vegetation to respawn and check z values.
helper.wait_for_condition(lambda: vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id) == expected_surface_tag_excluded_item_count, 5.0)
item_count = vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id)
Report.result(VegetationTests.testTag2_excluded_vegetation_count_correct, item_count == expected_surface_tag_excluded_item_count)
highest_z, lowest_z = FindHighestAndLowestZValuesInArea(test_aabb)
Report.result(VegetationTests.testTag2_excluded_vegetation_z_correct, lowest_z > box_height * gradient_value_1)
# Clear the filter and ensure vegetation respawns.
vegetation_entity.get_set_test(3, "Configuration|Exclusion|Surface Tags|[0]", surface_data.SurfaceTag("invalid"))
helper.wait_for_condition(lambda: vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id) == expected_no_exclusions_item_count, 5.0)
item_count = vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id)
Report.result(VegetationTests.cleared_exclusion_vegetation_count_correct, item_count == expected_no_exclusions_item_count)
# Exclude test_tag3 to exclude the higher items in terrain_entity_2 and recheck.
vegetation_entity.get_set_test(3, "Configuration|Exclusion|Surface Tags|[0]", surface_data.SurfaceTag("test_tag3"))
helper.wait_for_condition(lambda: vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id) == expected_surface_tag_excluded_item_count, 5.0)
item_count = vegetation.VegetationSpawnerRequestBus(bus.Event, "GetAreaProductCount", vegetation_entity.id)
Report.result(VegetationTests.testTag3_excluded_vegetation_count_correct, item_count == expected_surface_tag_excluded_item_count)
highest_z, lowest_z = FindHighestAndLowestZValuesInArea(test_aabb)
Report.result(VegetationTests.testTag3_excluded_vegetation_z_correct, highest_z < box_height * gradient_value_2)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(TerrainSystem_VegetationSpawnsOnTerrainSurfaces)

@ -72,7 +72,7 @@ def Terrain_SupportsPhysics():
# 2) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components
entity1_components_to_add = ["Axis Aligned Box Shape", "Terrain Layer Spawner", "Terrain Height Gradient List", "Terrain Physics Heightfield Collider", "PhysX Heightfield Collider"]
entity2_components_to_add = ["Vegetation Reference Shape", "Gradient Transform Modifier", "FastNoise Gradient"]
entity2_components_to_add = ["Shape Reference", "Gradient Transform Modifier", "FastNoise Gradient"]
ball_components_to_add = ["Sphere Shape", "PhysX Collider", "PhysX Rigid Body"]
terrain_spawner_entity = hydra.Entity("TestEntity1")
terrain_spawner_entity.create_entity(azmath.Vector3(512.0, 512.0, 50.0), entity1_components_to_add)

@ -27,3 +27,12 @@ class TestAutomation(EditorTestSuite):
class test_Terrain_SupportsPhysics(EditorSharedTest):
from .EditorScripts import Terrain_SupportsPhysics as test_module
class test_TerrainHeightGradientList_AddRemoveGradientWorks(EditorSharedTest):
from .EditorScripts import TerrainHeightGradientList_AddRemoveGradientWorks as test_module
class test_TerrainSystem_VegetationSpawnsOnTerrainSurfaces(EditorSharedTest):
from .EditorScripts import TerrainSystem_VegetationSpawnsOnTerrainSurfaces as test_module
class test_TerrainMacroMaterialComponent_MacroMaterialActivates(EditorSharedTest):
from .EditorScripts import TerrainMacroMaterialComponent_MacroMaterialActivates as test_module

@ -15,6 +15,7 @@ import logging
# Import LyTestTools
import ly_test_tools.o3de.asset_processor as asset_processor_commands
import ly_test_tools.o3de.asset_processor_utils
logger = logging.getLogger(__name__)
@ -36,5 +37,8 @@ def asset_processor(request: pytest.fixture, workspace: pytest.fixture) -> asset
ap.stop()
request.addfinalizer(teardown)
for n in ly_test_tools.o3de.asset_processor_utils.processList:
assert not ly_test_tools.o3de.asset_processor_utils.check_ap_running(n), f"{n} process did not shutdown correctly."
return ap

@ -8,6 +8,8 @@ General Asset Processor Batch Tests
"""
# Import builtin libraries
from os import listdir
import pytest
import logging
import os
@ -724,3 +726,11 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
assert error_line_found, "The error could not be found in the newest run of the AP Batch log."
@pytest.mark.assetpipeline
def test_AssetProcessor_Log_On_Failure(self, asset_processor, ap_setup_fixture, workspace):
asset_processor.prepare_test_environment(ap_setup_fixture["tests_dir"], "test_AP_Logs")
result, output = asset_processor.batch_process(expect_failure=True, capture_output=True)
assert result == False, f'AssetProcessorBatch should have failed because there is a bad asset, output was {output}'
jobLogs = listdir(workspace.paths.ap_job_logs() + "/test_AP_Logs")
assert not len(jobLogs) == 0, 'No job logs where output during failure.'

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7c6b33c6137d6bd8c696f180c30a23089c95c1af398a630b4b13e080bec3254d
size 18220

@ -8,7 +8,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
import os
import logging
import subprocess
import sys
import pytest
import time
@ -128,7 +128,8 @@ class TestAutomationBase:
errors.append(TestRunError("FAILED TEST", error_str))
if return_code and return_code != TestAutomationBase.TEST_FAIL_RETCODE: # Crashed
crash_info = "-- No crash log available --"
crash_log = os.path.join(workspace.paths.project_log(), 'error.log')
crash_log = workspace.paths.crash_log()
try:
waiter.wait_for(lambda: os.path.exists(crash_log), timeout=TestAutomationBase.WAIT_FOR_CRASH_LOG)
except AssertionError:

@ -72,9 +72,9 @@ def DynamicSliceInstanceSpawner_Embedded_E2E():
# 1) Create a new, temporary level
lvl_name = "tmp_level"
helper.init_idle()
level_created = general.create_level_no_prompt(lvl_name, 1024, 1, 4096, False)
level_created = helper.create_level(lvl_name)
general.idle_wait(1.0)
Report.critical_result(Tests.level_created, level_created == 0)
Report.critical_result(Tests.level_created, level_created)
general.set_current_view_position(512.0, 480.0, 38.0)
# 2) Create a new entity with required vegetation area components and Script Canvas component for launcher test

@ -73,9 +73,9 @@ def DynamicSliceInstanceSpawner_External_E2E():
# 1) Create a new, temporary level
lvl_name = "tmp_level"
helper.init_idle()
level_created = general.create_level_no_prompt(lvl_name, 1024, 1, 4096, False)
level_created = helper.create_level(lvl_name)
general.idle_wait(1.0)
Report.critical_result(Tests.level_created, level_created == 0)
Report.critical_result(Tests.level_created, level_created)
general.set_current_view_position(512.0, 480.0, 38.0)
# 2) Create a new entity with required vegetation area components and switch the Vegetation Asset List Source

@ -76,9 +76,9 @@ def LayerBlender_E2E_Editor():
# 1) Create a new, temporary level
lvl_name = "tmp_level"
helper.init_idle()
level_created = general.create_level_no_prompt(lvl_name, 1024, 1, 4096, False)
level_created = helper.create_level(lvl_name)
general.idle_wait(1.0)
Report.critical_result(Tests.level_created, level_created == 0)
Report.critical_result(Tests.level_created, level_created)
general.set_current_view_position(500.49, 498.69, 46.66)
general.set_current_view_rotation(-42.05, 0.00, -36.33)

@ -78,7 +78,7 @@ def LayerSpawner_InheritBehaviorFlag():
# Create Vegetation area and assign a valid asset
veg_1 = hydra.Entity("veg_1")
veg_1.create_entity(
position, ["Vegetation Layer Spawner", "Vegetation Reference Shape", "Vegetation Asset List"]
position, ["Vegetation Layer Spawner", "Shape Reference", "Vegetation Asset List"]
)
set_dynamic_slice_asset(veg_1, 2, os.path.join("Slices", "PinkFlower.dynamicslice"))
veg_1.get_set_test(1, "Configuration|Shape Entity Id", blender_entity.id)
@ -86,7 +86,7 @@ def LayerSpawner_InheritBehaviorFlag():
# Create second vegetation area and assign a valid asset
veg_2 = hydra.Entity("veg_2")
veg_2.create_entity(
position, ["Vegetation Layer Spawner", "Vegetation Reference Shape", "Vegetation Asset List"]
position, ["Vegetation Layer Spawner", "Shape Reference", "Vegetation Asset List"]
)
set_dynamic_slice_asset(veg_2, 2, os.path.join("Slices", "PurpleFlower.dynamicslice"))
veg_2.get_set_test(1, "Configuration|Shape Entity Id", blender_entity.id)

@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
def LayerSpawner_InstancesPlantInAllSupportedShapes():
"""
Summary:
The level is loaded and vegetation area is created. Then the Vegetation Reference Shape
The level is loaded and vegetation area is created. Then the Shape Reference
component of vegetation area is pinned with entities of different shape components to check
if the vegetation plants in different shaped areas.
@ -67,7 +67,7 @@ def LayerSpawner_InstancesPlantInAllSupportedShapes():
10.0, 10.0, 10.0,
asset_path)
vegetation.remove_component("Box Shape")
vegetation.add_component("Vegetation Reference Shape")
vegetation.add_component("Shape Reference")
# Create surface for planting on
dynveg.create_surface_entity("Surface Entity", entity_position, 60.0, 60.0, 1.0)

@ -19,6 +19,17 @@ class TestAutomation(EditorTestSuite):
enable_prefab_system = False
# Helpers for test asset cleanup
def cleanup_test_level(self, workspace):
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
True, True)
def cleanup_test_slices(self, workspace):
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices",
"TestSlice_1.slice")], True, True)
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices",
"TestSlice_2.slice")], True, True)
class test_DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks(EditorParallelTest):
from .EditorScripts import DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks as test_module
@ -37,10 +48,7 @@ class TestAutomation(EditorTestSuite):
class test_SpawnerSlices_SliceCreationAndVisibilityToggleWorks(EditorSingleTest):
# Custom teardown to remove slice asset created during test
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices",
"TestSlice_1.slice")], True, True)
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices",
"TestSlice_2.slice")], True, True)
TestAutomation.cleanup_test_slices(self, workspace)
from .EditorScripts import SpawnerSlices_SliceCreationAndVisibilityToggleWorks as test_module
class test_AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea(EditorParallelTest):
@ -148,29 +156,32 @@ class TestAutomation(EditorTestSuite):
class test_SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlopes(EditorParallelTest):
from .EditorScripts import SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlope as test_module
@pytest.mark.xfail(reason="Intermittently fails to create level")
class test_DynamicSliceInstanceSpawner_Embedded_E2E_Editor(EditorSingleTest):
from .EditorScripts import DynamicSliceInstanceSpawner_Embedded_E2E as test_module
# Custom teardown to remove test level created during test
# Custom setup/teardown to remove test level created during test
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
TestAutomation.cleanup_test_level(self, workspace)
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
True, True)
TestAutomation.cleanup_test_level(self, workspace)
@pytest.mark.xfail(reason="Intermittently fails to create level")
class test_DynamicSliceInstanceSpawner_External_E2E_Editor(EditorSingleTest):
from .EditorScripts import DynamicSliceInstanceSpawner_External_E2E as test_module
# Custom teardown to remove test level created during test
# Custom setup/teardown to remove test level created during test
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
TestAutomation.cleanup_test_level(self, workspace)
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
True, True)
TestAutomation.cleanup_test_level(self, workspace)
@pytest.mark.xfail(reason="Intermittently fails to create level")
class test_LayerBlender_E2E_Editor(EditorSingleTest):
from .EditorScripts import LayerBlender_E2E_Editor as test_module
# Custom teardown to remove test level created during test
# Custom setup/teardown to remove test level created during test
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
TestAutomation.cleanup_test_level(self, workspace)
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
True, True)
TestAutomation.cleanup_test_level(self, workspace)

@ -96,7 +96,7 @@ def AreaNodes_DependentComponentsAdded():
'SpawnerAreaNode': [
'Vegetation Layer Spawner',
'Vegetation Asset List',
'Vegetation Reference Shape'
'Shape Reference'
],
'MeshBlockerAreaNode': [
'Vegetation Layer Blocker (Mesh)',
@ -104,7 +104,7 @@ def AreaNodes_DependentComponentsAdded():
],
'BlockerAreaNode': [
'Vegetation Layer Blocker',
'Vegetation Reference Shape'
'Shape Reference'
]
}

@ -82,7 +82,7 @@ def Edit_DisabledNodeDuplication():
nodes = {
'SpawnerAreaNode': 'Vegetation Asset List',
'MeshBlockerAreaNode': 'Mesh',
'BlockerAreaNode': 'Vegetation Reference Shape',
'BlockerAreaNode': 'Shape Reference',
'FastNoiseGradientNode': 'Gradient Transform Modifier',
'ImageGradientNode': 'Gradient Transform Modifier',
'PerlinNoiseGradientNode': 'Gradient Transform Modifier',

@ -104,7 +104,7 @@ def GradientNodes_DependentComponentsAdded():
# we will be checking for
commonComponents = [
'Gradient Transform Modifier',
'Vegetation Reference Shape'
'Shape Reference'
]
componentNames = []
for name in gradients:
@ -114,7 +114,7 @@ def GradientNodes_DependentComponentsAdded():
# Create nodes for the gradients that have additional required dependencies and check if
# the Entity created by adding the node has the appropriate Component and required
# Gradient Transform Modifier and Vegetation Reference Shape components added automatically to it
# Gradient Transform Modifier and Shape Reference components added automatically to it
newGraph = graph.GraphManagerRequestBus(bus.Broadcast, 'GetGraph', newGraphId)
x = 10.0
y = 10.0

@ -0,0 +1,12 @@
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7e169277bca473325281d5fe043cffc9196bd3ef46f6bffbea6e0b5e3b7194a1
size 62700

@ -0,0 +1,8 @@
{
"values": [
{
"$type": "ScriptProcessorRule",
"scriptFilename": "Editor/Scripts/auto_lod.py"
}
]
}

@ -10,6 +10,8 @@
#include "EditorPreferencesPageViewportManipulator.h"
#include <AzToolsFramework/Viewport/ViewportSettings.h>
// Editor
#include "EditorViewportSettings.h"
#include "Settings.h"
@ -19,7 +21,17 @@ void CEditorPreferencesPage_ViewportManipulator::Reflect(AZ::SerializeContext& s
serialize.Class<Manipulators>()
->Version(1)
->Field("LineBoundWidth", &Manipulators::m_manipulatorLineBoundWidth)
->Field("CircleBoundWidth", &Manipulators::m_manipulatorCircleBoundWidth);
->Field("CircleBoundWidth", &Manipulators::m_manipulatorCircleBoundWidth)
->Field("LinearManipulatorAxisLength", &Manipulators::m_linearManipulatorAxisLength)
->Field("PlanarManipulatorAxisLength", &Manipulators::m_planarManipulatorAxisLength)
->Field("SurfaceManipulatorRadius", &Manipulators::m_surfaceManipulatorRadius)
->Field("SurfaceManipulatorOpacity", &Manipulators::m_surfaceManipulatorOpacity)
->Field("LinearManipulatorConeLength", &Manipulators::m_linearManipulatorConeLength)
->Field("LinearManipulatorConeRadius", &Manipulators::m_linearManipulatorConeRadius)
->Field("ScaleManipulatorBoxHalfExtent", &Manipulators::m_scaleManipulatorBoxHalfExtent)
->Field("RotationManipulatorRadius", &Manipulators::m_rotationManipulatorRadius)
->Field("ManipulatorViewBaseScale", &Manipulators::m_manipulatorViewBaseScale)
->Field("FlipManipulatorAxesTowardsView", &Manipulators::m_flipManipulatorAxesTowardsView);
serialize.Class<CEditorPreferencesPage_ViewportManipulator>()->Version(2)->Field(
"Manipulators", &CEditorPreferencesPage_ViewportManipulator::m_manipulators);
@ -36,7 +48,55 @@ void CEditorPreferencesPage_ViewportManipulator::Reflect(AZ::SerializeContext& s
AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_manipulatorCircleBoundWidth, "Circle Bound Width",
"Manipulator Circle Bound Width")
->Attribute(AZ::Edit::Attributes::Min, 0.001f)
->Attribute(AZ::Edit::Attributes::Max, 2.0f);
->Attribute(AZ::Edit::Attributes::Max, 2.0f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_linearManipulatorAxisLength, "Linear Manipulator Axis Length",
"Length of default Linear Manipulator (for Translation and Scale Manipulators)")
->Attribute(AZ::Edit::Attributes::Min, 0.1f)
->Attribute(AZ::Edit::Attributes::Max, 5.0f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_planarManipulatorAxisLength, "Planar Manipulator Axis Length",
"Length of default Planar Manipulator (for Translation Manipulators)")
->Attribute(AZ::Edit::Attributes::Min, 0.1f)
->Attribute(AZ::Edit::Attributes::Max, 5.0f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_surfaceManipulatorRadius, "Surface Manipulator Radius",
"Radius of default Surface Manipulator (for Translation Manipulators)")
->Attribute(AZ::Edit::Attributes::Min, 0.05f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_surfaceManipulatorOpacity, "Surface Manipulator Opacity",
"Opacity of default Surface Manipulator (for Translation Manipulators)")
->Attribute(AZ::Edit::Attributes::Min, 0.01f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_linearManipulatorConeLength, "Linear Manipulator Cone Length",
"Length of cone for default Linear Manipulator (for Translation Manipulators)")
->Attribute(AZ::Edit::Attributes::Min, 0.05f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_linearManipulatorConeRadius, "Linear Manipulator Cone Radius",
"Radius of cone for default Linear Manipulator (for Translation Manipulators)")
->Attribute(AZ::Edit::Attributes::Min, 0.05f)
->Attribute(AZ::Edit::Attributes::Max, 0.5f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_scaleManipulatorBoxHalfExtent, "Scale Manipulator Box Half Extent",
"Half extent of box for default Scale Manipulator")
->Attribute(AZ::Edit::Attributes::Min, 0.05f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_rotationManipulatorRadius, "Rotation Manipulator Radius",
"Radius of default Angular Manipulators (for Rotation Manipulators)")
->Attribute(AZ::Edit::Attributes::Min, 0.5f)
->Attribute(AZ::Edit::Attributes::Max, 5.0f)
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_manipulatorViewBaseScale, "Manipulator View Base Scale",
"The base scale to apply to all Manipulator Views (default is 1.0)")
->Attribute(AZ::Edit::Attributes::Min, 0.5f)
->Attribute(AZ::Edit::Attributes::Max, 2.0f)
->DataElement(
AZ::Edit::UIHandlers::CheckBox, &Manipulators::m_flipManipulatorAxesTowardsView, "Flip Manipulator Axes Towards View",
"Determines whether Planar and Linear Manipulators should switch to face the view (camera) in the Editor");
editContext
->Class<CEditorPreferencesPage_ViewportManipulator>("Manipulator Viewport Preferences", "Manipulator Viewport Preferences")
@ -82,10 +142,32 @@ void CEditorPreferencesPage_ViewportManipulator::OnApply()
{
SandboxEditor::SetManipulatorLineBoundWidth(m_manipulators.m_manipulatorLineBoundWidth);
SandboxEditor::SetManipulatorCircleBoundWidth(m_manipulators.m_manipulatorCircleBoundWidth);
AzToolsFramework::SetLinearManipulatorAxisLength(m_manipulators.m_linearManipulatorAxisLength);
AzToolsFramework::SetPlanarManipulatorAxisLength(m_manipulators.m_planarManipulatorAxisLength);
AzToolsFramework::SetSurfaceManipulatorRadius(m_manipulators.m_surfaceManipulatorRadius);
AzToolsFramework::SetSurfaceManipulatorOpacity(m_manipulators.m_surfaceManipulatorOpacity);
AzToolsFramework::SetLinearManipulatorConeLength(m_manipulators.m_linearManipulatorConeLength);
AzToolsFramework::SetLinearManipulatorConeRadius(m_manipulators.m_linearManipulatorConeRadius);
AzToolsFramework::SetScaleManipulatorBoxHalfExtent(m_manipulators.m_scaleManipulatorBoxHalfExtent);
AzToolsFramework::SetRotationManipulatorRadius(m_manipulators.m_rotationManipulatorRadius);
AzToolsFramework::SetFlipManipulatorAxesTowardsView(m_manipulators.m_flipManipulatorAxesTowardsView);
AzToolsFramework::SetManipulatorViewBaseScale(m_manipulators.m_manipulatorViewBaseScale);
}
void CEditorPreferencesPage_ViewportManipulator::InitializeSettings()
{
m_manipulators.m_manipulatorLineBoundWidth = SandboxEditor::ManipulatorLineBoundWidth();
m_manipulators.m_manipulatorCircleBoundWidth = SandboxEditor::ManipulatorCircleBoundWidth();
m_manipulators.m_linearManipulatorAxisLength = AzToolsFramework::LinearManipulatorAxisLength();
m_manipulators.m_planarManipulatorAxisLength = AzToolsFramework::PlanarManipulatorAxisLength();
m_manipulators.m_surfaceManipulatorRadius = AzToolsFramework::SurfaceManipulatorRadius();
m_manipulators.m_surfaceManipulatorOpacity = AzToolsFramework::SurfaceManipulatorOpacity();
m_manipulators.m_linearManipulatorConeLength = AzToolsFramework::LinearManipulatorConeLength();
m_manipulators.m_linearManipulatorConeRadius = AzToolsFramework::LinearManipulatorConeRadius();
m_manipulators.m_scaleManipulatorBoxHalfExtent = AzToolsFramework::ScaleManipulatorBoxHalfExtent();
m_manipulators.m_rotationManipulatorRadius = AzToolsFramework::RotationManipulatorRadius();
m_manipulators.m_flipManipulatorAxesTowardsView = AzToolsFramework::FlipManipulatorAxesTowardsView();
m_manipulators.m_manipulatorViewBaseScale = AzToolsFramework::ManipulatorViewBaseScale();
}

@ -41,6 +41,16 @@ private:
float m_manipulatorLineBoundWidth = 0.0f;
float m_manipulatorCircleBoundWidth = 0.0f;
float m_linearManipulatorAxisLength = 0.0f;
float m_planarManipulatorAxisLength = 0.0f;
float m_surfaceManipulatorRadius = 0.0f;
float m_surfaceManipulatorOpacity = 0.0f;
float m_linearManipulatorConeLength = 0.0f;
float m_linearManipulatorConeRadius = 0.0f;
float m_scaleManipulatorBoxHalfExtent = 0.0f;
float m_rotationManipulatorRadius = 0.0f;
float m_manipulatorViewBaseScale = 0.0f;
bool m_flipManipulatorAxesTowardsView = false;
};
Manipulators m_manipulators;

@ -12,6 +12,7 @@
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/std/string/string_view.h>
#include <AzToolsFramework/Viewport/ViewportSettings.h>
namespace SandboxEditor
{
@ -57,31 +58,6 @@ namespace SandboxEditor
constexpr AZStd::string_view CameraDefaultStartingPositionY = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/y";
constexpr AZStd::string_view CameraDefaultStartingPositionZ = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/z";
template<typename T>
void SetRegistry(const AZStd::string_view setting, T&& value)
{
if (auto* registry = AZ::SettingsRegistry::Get())
{
registry->Set(setting, AZStd::forward<T>(value));
}
}
template<typename T>
AZStd::remove_cvref_t<T> GetRegistry(const AZStd::string_view setting, T&& defaultValue)
{
AZStd::remove_cvref_t<T> value = AZStd::forward<T>(defaultValue);
if (const auto* registry = AZ::SettingsRegistry::Get())
{
T potentialValue;
if (registry->Get(potentialValue, setting))
{
value = AZStd::move(potentialValue);
}
}
return value;
}
struct EditorViewportSettingsCallbacksImpl : public EditorViewportSettingsCallbacks
{
EditorViewportSettingsCallbacksImpl()
@ -118,399 +94,409 @@ namespace SandboxEditor
AZ::Vector3 CameraDefaultEditorPosition()
{
return AZ::Vector3(
aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionX, 0.0)),
aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionY, -10.0)),
aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionZ, 4.0)));
aznumeric_cast<float>(AzToolsFramework::GetRegistry(CameraDefaultStartingPositionX, 0.0)),
aznumeric_cast<float>(AzToolsFramework::GetRegistry(CameraDefaultStartingPositionY, -10.0)),
aznumeric_cast<float>(AzToolsFramework::GetRegistry(CameraDefaultStartingPositionZ, 4.0)));
}
void SetCameraDefaultEditorPosition(const AZ::Vector3& defaultCameraPosition)
{
SetRegistry(CameraDefaultStartingPositionX, defaultCameraPosition.GetX());
SetRegistry(CameraDefaultStartingPositionY, defaultCameraPosition.GetY());
SetRegistry(CameraDefaultStartingPositionZ, defaultCameraPosition.GetZ());
AzToolsFramework::SetRegistry(CameraDefaultStartingPositionX, defaultCameraPosition.GetX());
AzToolsFramework::SetRegistry(CameraDefaultStartingPositionY, defaultCameraPosition.GetY());
AzToolsFramework::SetRegistry(CameraDefaultStartingPositionZ, defaultCameraPosition.GetZ());
}
AZ::u64 MaxItemsShownInAssetBrowserSearch()
{
return GetRegistry(AssetBrowserMaxItemsShownInSearchSetting, aznumeric_cast<AZ::u64>(50));
return AzToolsFramework::GetRegistry(AssetBrowserMaxItemsShownInSearchSetting, aznumeric_cast<AZ::u64>(50));
}
void SetMaxItemsShownInAssetBrowserSearch(const AZ::u64 numberOfItemsShown)
{
SetRegistry(AssetBrowserMaxItemsShownInSearchSetting, numberOfItemsShown);
AzToolsFramework::SetRegistry(AssetBrowserMaxItemsShownInSearchSetting, numberOfItemsShown);
}
bool GridSnappingEnabled()
{
return GetRegistry(GridSnappingSetting, false);
return AzToolsFramework::GetRegistry(GridSnappingSetting, false);
}
void SetGridSnapping(const bool enabled)
{
SetRegistry(GridSnappingSetting, enabled);
AzToolsFramework::SetRegistry(GridSnappingSetting, enabled);
}
float GridSnappingSize()
{
return aznumeric_cast<float>(GetRegistry(GridSizeSetting, 0.1));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(GridSizeSetting, 0.1));
}
void SetGridSnappingSize(const float size)
{
SetRegistry(GridSizeSetting, size);
AzToolsFramework::SetRegistry(GridSizeSetting, size);
}
bool AngleSnappingEnabled()
{
return GetRegistry(AngleSnappingSetting, false);
return AzToolsFramework::GetRegistry(AngleSnappingSetting, false);
}
void SetAngleSnapping(const bool enabled)
{
SetRegistry(AngleSnappingSetting, enabled);
AzToolsFramework::SetRegistry(AngleSnappingSetting, enabled);
}
float AngleSnappingSize()
{
return aznumeric_cast<float>(GetRegistry(AngleSizeSetting, 5.0));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(AngleSizeSetting, 5.0));
}
void SetAngleSnappingSize(const float size)
{
SetRegistry(AngleSizeSetting, size);
AzToolsFramework::SetRegistry(AngleSizeSetting, size);
}
bool ShowingGrid()
{
return GetRegistry(ShowGridSetting, false);
return AzToolsFramework::GetRegistry(ShowGridSetting, false);
}
void SetShowingGrid(const bool showing)
{
SetRegistry(ShowGridSetting, showing);
AzToolsFramework::SetRegistry(ShowGridSetting, showing);
}
bool StickySelectEnabled()
{
return GetRegistry(StickySelectSetting, false);
return AzToolsFramework::GetRegistry(StickySelectSetting, false);
}
void SetStickySelectEnabled(const bool enabled)
{
SetRegistry(StickySelectSetting, enabled);
AzToolsFramework::SetRegistry(StickySelectSetting, enabled);
}
float ManipulatorLineBoundWidth()
{
return aznumeric_cast<float>(GetRegistry(ManipulatorLineBoundWidthSetting, 0.1));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(ManipulatorLineBoundWidthSetting, 0.1));
}
void SetManipulatorLineBoundWidth(const float lineBoundWidth)
{
SetRegistry(ManipulatorLineBoundWidthSetting, lineBoundWidth);
AzToolsFramework::SetRegistry(ManipulatorLineBoundWidthSetting, lineBoundWidth);
}
float ManipulatorCircleBoundWidth()
{
return aznumeric_cast<float>(GetRegistry(ManipulatorCircleBoundWidthSetting, 0.1));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(ManipulatorCircleBoundWidthSetting, 0.1));
}
void SetManipulatorCircleBoundWidth(const float circleBoundWidth)
{
SetRegistry(ManipulatorCircleBoundWidthSetting, circleBoundWidth);
AzToolsFramework::SetRegistry(ManipulatorCircleBoundWidthSetting, circleBoundWidth);
}
float CameraTranslateSpeed()
{
return aznumeric_cast<float>(GetRegistry(CameraTranslateSpeedSetting, 10.0));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(CameraTranslateSpeedSetting, 10.0));
}
void SetCameraTranslateSpeed(const float speed)
{
SetRegistry(CameraTranslateSpeedSetting, speed);
AzToolsFramework::SetRegistry(CameraTranslateSpeedSetting, speed);
}
float CameraBoostMultiplier()
{
return aznumeric_cast<float>(GetRegistry(CameraBoostMultiplierSetting, 3.0));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(CameraBoostMultiplierSetting, 3.0));
}
void SetCameraBoostMultiplier(const float multiplier)
{
SetRegistry(CameraBoostMultiplierSetting, multiplier);
AzToolsFramework::SetRegistry(CameraBoostMultiplierSetting, multiplier);
}
float CameraRotateSpeed()
{
return aznumeric_cast<float>(GetRegistry(CameraRotateSpeedSetting, 0.005));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(CameraRotateSpeedSetting, 0.005));
}
void SetCameraRotateSpeed(const float speed)
{
SetRegistry(CameraRotateSpeedSetting, speed);
AzToolsFramework::SetRegistry(CameraRotateSpeedSetting, speed);
}
float CameraScrollSpeed()
{
return aznumeric_cast<float>(GetRegistry(CameraScrollSpeedSetting, 0.02));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(CameraScrollSpeedSetting, 0.02));
}
void SetCameraScrollSpeed(const float speed)
{
SetRegistry(CameraScrollSpeedSetting, speed);
AzToolsFramework::SetRegistry(CameraScrollSpeedSetting, speed);
}
float CameraDollyMotionSpeed()
{
return aznumeric_cast<float>(GetRegistry(CameraDollyMotionSpeedSetting, 0.01));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(CameraDollyMotionSpeedSetting, 0.01));
}
void SetCameraDollyMotionSpeed(const float speed)
{
SetRegistry(CameraDollyMotionSpeedSetting, speed);
AzToolsFramework::SetRegistry(CameraDollyMotionSpeedSetting, speed);
}
bool CameraOrbitYawRotationInverted()
{
return GetRegistry(CameraOrbitYawRotationInvertedSetting, false);
return AzToolsFramework::GetRegistry(CameraOrbitYawRotationInvertedSetting, false);
}
void SetCameraOrbitYawRotationInverted(const bool inverted)
{
SetRegistry(CameraOrbitYawRotationInvertedSetting, inverted);
AzToolsFramework::SetRegistry(CameraOrbitYawRotationInvertedSetting, inverted);
}
bool CameraPanInvertedX()
{
return GetRegistry(CameraPanInvertedXSetting, true);
return AzToolsFramework::GetRegistry(CameraPanInvertedXSetting, true);
}
void SetCameraPanInvertedX(const bool inverted)
{
SetRegistry(CameraPanInvertedXSetting, inverted);
AzToolsFramework::SetRegistry(CameraPanInvertedXSetting, inverted);
}
bool CameraPanInvertedY()
{
return GetRegistry(CameraPanInvertedYSetting, true);
return AzToolsFramework::GetRegistry(CameraPanInvertedYSetting, true);
}
void SetCameraPanInvertedY(const bool inverted)
{
SetRegistry(CameraPanInvertedYSetting, inverted);
AzToolsFramework::SetRegistry(CameraPanInvertedYSetting, inverted);
}
float CameraPanSpeed()
{
return aznumeric_cast<float>(GetRegistry(CameraPanSpeedSetting, 0.01));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(CameraPanSpeedSetting, 0.01));
}
void SetCameraPanSpeed(float speed)
{
SetRegistry(CameraPanSpeedSetting, speed);
AzToolsFramework::SetRegistry(CameraPanSpeedSetting, speed);
}
float CameraRotateSmoothness()
{
return aznumeric_cast<float>(GetRegistry(CameraRotateSmoothnessSetting, 5.0));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(CameraRotateSmoothnessSetting, 5.0));
}
void SetCameraRotateSmoothness(const float smoothness)
{
SetRegistry(CameraRotateSmoothnessSetting, smoothness);
AzToolsFramework::SetRegistry(CameraRotateSmoothnessSetting, smoothness);
}
float CameraTranslateSmoothness()
{
return aznumeric_cast<float>(GetRegistry(CameraTranslateSmoothnessSetting, 5.0));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(CameraTranslateSmoothnessSetting, 5.0));
}
void SetCameraTranslateSmoothness(const float smoothness)
{
SetRegistry(CameraTranslateSmoothnessSetting, smoothness);
AzToolsFramework::SetRegistry(CameraTranslateSmoothnessSetting, smoothness);
}
bool CameraRotateSmoothingEnabled()
{
return GetRegistry(CameraRotateSmoothingSetting, true);
return AzToolsFramework::GetRegistry(CameraRotateSmoothingSetting, true);
}
void SetCameraRotateSmoothingEnabled(const bool enabled)
{
SetRegistry(CameraRotateSmoothingSetting, enabled);
AzToolsFramework::SetRegistry(CameraRotateSmoothingSetting, enabled);
}
bool CameraTranslateSmoothingEnabled()
{
return GetRegistry(CameraTranslateSmoothingSetting, true);
return AzToolsFramework::GetRegistry(CameraTranslateSmoothingSetting, true);
}
void SetCameraTranslateSmoothingEnabled(const bool enabled)
{
SetRegistry(CameraTranslateSmoothingSetting, enabled);
AzToolsFramework::SetRegistry(CameraTranslateSmoothingSetting, enabled);
}
bool CameraCaptureCursorForLook()
{
return GetRegistry(CameraCaptureCursorLookSetting, true);
return AzToolsFramework::GetRegistry(CameraCaptureCursorLookSetting, true);
}
void SetCameraCaptureCursorForLook(const bool capture)
{
SetRegistry(CameraCaptureCursorLookSetting, capture);
AzToolsFramework::SetRegistry(CameraCaptureCursorLookSetting, capture);
}
float CameraDefaultOrbitDistance()
{
return aznumeric_cast<float>(GetRegistry(CameraDefaultOrbitDistanceSetting, 20.0));
return aznumeric_cast<float>(AzToolsFramework::GetRegistry(CameraDefaultOrbitDistanceSetting, 20.0));
}
void SetCameraDefaultOrbitDistance(const float distance)
{
SetRegistry(CameraDefaultOrbitDistanceSetting, distance);
AzToolsFramework::SetRegistry(CameraDefaultOrbitDistanceSetting, distance);
}
AzFramework::InputChannelId CameraTranslateForwardChannelId()
{
return AzFramework::InputChannelId(
GetRegistry(CameraTranslateForwardIdSetting, AZStd::string("keyboard_key_alphanumeric_W")).c_str());
AzToolsFramework::GetRegistry(CameraTranslateForwardIdSetting, AZStd::string("keyboard_key_alphanumeric_W")).c_str());
}
void SetCameraTranslateForwardChannelId(AZStd::string_view cameraTranslateForwardId)
{
SetRegistry(CameraTranslateForwardIdSetting, cameraTranslateForwardId);
AzToolsFramework::SetRegistry(CameraTranslateForwardIdSetting, cameraTranslateForwardId);
}
AzFramework::InputChannelId CameraTranslateBackwardChannelId()
{
return AzFramework::InputChannelId(
GetRegistry(CameraTranslateBackwardIdSetting, AZStd::string("keyboard_key_alphanumeric_S")).c_str());
AzToolsFramework::GetRegistry(CameraTranslateBackwardIdSetting, AZStd::string("keyboard_key_alphanumeric_S")).c_str());
}
void SetCameraTranslateBackwardChannelId(AZStd::string_view cameraTranslateBackwardId)
{
SetRegistry(CameraTranslateBackwardIdSetting, cameraTranslateBackwardId);
AzToolsFramework::SetRegistry(CameraTranslateBackwardIdSetting, cameraTranslateBackwardId);
}
AzFramework::InputChannelId CameraTranslateLeftChannelId()
{
return AzFramework::InputChannelId(GetRegistry(CameraTranslateLeftIdSetting, AZStd::string("keyboard_key_alphanumeric_A")).c_str());
return AzFramework::InputChannelId(
AzToolsFramework::GetRegistry(CameraTranslateLeftIdSetting, AZStd::string("keyboard_key_alphanumeric_A")).c_str());
}
void SetCameraTranslateLeftChannelId(AZStd::string_view cameraTranslateLeftId)
{
SetRegistry(CameraTranslateLeftIdSetting, cameraTranslateLeftId);
AzToolsFramework::SetRegistry(CameraTranslateLeftIdSetting, cameraTranslateLeftId);
}
AzFramework::InputChannelId CameraTranslateRightChannelId()
{
return AzFramework::InputChannelId(
GetRegistry(CameraTranslateRightIdSetting, AZStd::string("keyboard_key_alphanumeric_D")).c_str());
AzToolsFramework::GetRegistry(CameraTranslateRightIdSetting, AZStd::string("keyboard_key_alphanumeric_D")).c_str());
}
void SetCameraTranslateRightChannelId(AZStd::string_view cameraTranslateRightId)
{
SetRegistry(CameraTranslateRightIdSetting, cameraTranslateRightId);
AzToolsFramework::SetRegistry(CameraTranslateRightIdSetting, cameraTranslateRightId);
}
AzFramework::InputChannelId CameraTranslateUpChannelId()
{
return AzFramework::InputChannelId(GetRegistry(CameraTranslateUpIdSetting, AZStd::string("keyboard_key_alphanumeric_E")).c_str());
return AzFramework::InputChannelId(
AzToolsFramework::GetRegistry(CameraTranslateUpIdSetting, AZStd::string("keyboard_key_alphanumeric_E")).c_str());
}
void SetCameraTranslateUpChannelId(AZStd::string_view cameraTranslateUpId)
{
SetRegistry(CameraTranslateUpIdSetting, cameraTranslateUpId);
AzToolsFramework::SetRegistry(CameraTranslateUpIdSetting, cameraTranslateUpId);
}
AzFramework::InputChannelId CameraTranslateDownChannelId()
{
return AzFramework::InputChannelId(GetRegistry(CameraTranslateDownIdSetting, AZStd::string("keyboard_key_alphanumeric_Q")).c_str());
return AzFramework::InputChannelId(
AzToolsFramework::GetRegistry(CameraTranslateDownIdSetting, AZStd::string("keyboard_key_alphanumeric_Q")).c_str());
}
void SetCameraTranslateDownChannelId(AZStd::string_view cameraTranslateDownId)
{
SetRegistry(CameraTranslateDownIdSetting, cameraTranslateDownId);
AzToolsFramework::SetRegistry(CameraTranslateDownIdSetting, cameraTranslateDownId);
}
AzFramework::InputChannelId CameraTranslateBoostChannelId()
{
return AzFramework::InputChannelId(
GetRegistry(CameraTranslateBoostIdSetting, AZStd::string("keyboard_key_modifier_shift_l")).c_str());
AzToolsFramework::GetRegistry(CameraTranslateBoostIdSetting, AZStd::string("keyboard_key_modifier_shift_l")).c_str());
}
void SetCameraTranslateBoostChannelId(AZStd::string_view cameraTranslateBoostId)
{
SetRegistry(CameraTranslateBoostIdSetting, cameraTranslateBoostId);
AzToolsFramework::SetRegistry(CameraTranslateBoostIdSetting, cameraTranslateBoostId);
}
AzFramework::InputChannelId CameraOrbitChannelId()
{
return AzFramework::InputChannelId(GetRegistry(CameraOrbitIdSetting, AZStd::string("keyboard_key_modifier_alt_l")).c_str());
return AzFramework::InputChannelId(
AzToolsFramework::GetRegistry(CameraOrbitIdSetting, AZStd::string("keyboard_key_modifier_alt_l")).c_str());
}
void SetCameraOrbitChannelId(AZStd::string_view cameraOrbitId)
{
SetRegistry(CameraOrbitIdSetting, cameraOrbitId);
AzToolsFramework::SetRegistry(CameraOrbitIdSetting, cameraOrbitId);
}
AzFramework::InputChannelId CameraFreeLookChannelId()
{
return AzFramework::InputChannelId(GetRegistry(CameraFreeLookIdSetting, AZStd::string("mouse_button_right")).c_str());
return AzFramework::InputChannelId(
AzToolsFramework::GetRegistry(CameraFreeLookIdSetting, AZStd::string("mouse_button_right")).c_str());
}
void SetCameraFreeLookChannelId(AZStd::string_view cameraFreeLookId)
{
SetRegistry(CameraFreeLookIdSetting, cameraFreeLookId);
AzToolsFramework::SetRegistry(CameraFreeLookIdSetting, cameraFreeLookId);
}
AzFramework::InputChannelId CameraFreePanChannelId()
{
return AzFramework::InputChannelId(GetRegistry(CameraFreePanIdSetting, AZStd::string("mouse_button_middle")).c_str());
return AzFramework::InputChannelId(
AzToolsFramework::GetRegistry(CameraFreePanIdSetting, AZStd::string("mouse_button_middle")).c_str());
}
void SetCameraFreePanChannelId(AZStd::string_view cameraFreePanId)
{
SetRegistry(CameraFreePanIdSetting, cameraFreePanId);
AzToolsFramework::SetRegistry(CameraFreePanIdSetting, cameraFreePanId);
}
AzFramework::InputChannelId CameraOrbitLookChannelId()
{
return AzFramework::InputChannelId(GetRegistry(CameraOrbitLookIdSetting, AZStd::string("mouse_button_left")).c_str());
return AzFramework::InputChannelId(
AzToolsFramework::GetRegistry(CameraOrbitLookIdSetting, AZStd::string("mouse_button_left")).c_str());
}
void SetCameraOrbitLookChannelId(AZStd::string_view cameraOrbitLookId)
{
SetRegistry(CameraOrbitLookIdSetting, cameraOrbitLookId);
AzToolsFramework::SetRegistry(CameraOrbitLookIdSetting, cameraOrbitLookId);
}
AzFramework::InputChannelId CameraOrbitDollyChannelId()
{
return AzFramework::InputChannelId(GetRegistry(CameraOrbitDollyIdSetting, AZStd::string("mouse_button_right")).c_str());
return AzFramework::InputChannelId(
AzToolsFramework::GetRegistry(CameraOrbitDollyIdSetting, AZStd::string("mouse_button_right")).c_str());
}
void SetCameraOrbitDollyChannelId(AZStd::string_view cameraOrbitDollyId)
{
SetRegistry(CameraOrbitDollyIdSetting, cameraOrbitDollyId);
AzToolsFramework::SetRegistry(CameraOrbitDollyIdSetting, cameraOrbitDollyId);
}
AzFramework::InputChannelId CameraOrbitPanChannelId()
{
return AzFramework::InputChannelId(GetRegistry(CameraOrbitPanIdSetting, AZStd::string("mouse_button_middle")).c_str());
return AzFramework::InputChannelId(
AzToolsFramework::GetRegistry(CameraOrbitPanIdSetting, AZStd::string("mouse_button_middle")).c_str());
}
void SetCameraOrbitPanChannelId(AZStd::string_view cameraOrbitPanId)
{
SetRegistry(CameraOrbitPanIdSetting, cameraOrbitPanId);
AzToolsFramework::SetRegistry(CameraOrbitPanIdSetting, cameraOrbitPanId);
}
AzFramework::InputChannelId CameraFocusChannelId()
{
return AzFramework::InputChannelId(GetRegistry(CameraFocusIdSetting, AZStd::string("keyboard_key_alphanumeric_X")).c_str());
return AzFramework::InputChannelId(
AzToolsFramework::GetRegistry(CameraFocusIdSetting, AZStd::string("keyboard_key_alphanumeric_X")).c_str());
}
void SetCameraFocusChannelId(AZStd::string_view cameraFocusId)
{
SetRegistry(CameraFocusIdSetting, cameraFocusId);
AzToolsFramework::SetRegistry(CameraFocusIdSetting, cameraFocusId);
}
} // namespace SandboxEditor

@ -35,7 +35,6 @@
// CryCommon
#include <CryCommon/INavigationSystem.h>
#include <CryCommon/LyShine/ILyShine.h>
#include <CryCommon/MainThreadRenderRequestBus.h>
// Editor
@ -595,13 +594,6 @@ void CGameEngine::SwitchToInEditor()
// Enable accelerators.
GetIEditor()->EnableAcceleratos(true);
// reset UI system
if (gEnv->pLyShine)
{
gEnv->pLyShine->Reset();
}
// [Anton] - order changed, see comments for CGameEngine::SetSimulationMode
//! Send event to switch out of game.
GetIEditor()->GetObjectManager()->SendEvent(EVENT_OUTOFGAME);

@ -19,7 +19,7 @@ using namespace ::testing;
namespace UnitTest
{
class TestingClickableLabel
: public testing::Test
: public ScopedAllocatorSetupFixture
{
public:
ClickableLabel m_clickableLabel;

@ -11,6 +11,7 @@
#include <AzCore/base.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
@ -22,7 +23,7 @@ namespace CryEditDocPythonBindingsUnitTests
{
class CryEditDocPythonBindingsFixture
: public testing::Test
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -30,7 +31,6 @@ namespace CryEditDocPythonBindingsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -11,6 +11,7 @@
#include <AzCore/base.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
@ -24,7 +25,7 @@ namespace CryEditPythonBindingsUnitTests
{
class CryEditPythonBindingsFixture
: public testing::Test
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -32,7 +33,6 @@ namespace CryEditPythonBindingsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -11,6 +11,7 @@
#include <AzCore/base.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
@ -22,7 +23,7 @@ namespace DisplaySettingsPythonBindingsUnitTests
{
class DisplaySettingsPythonBindingsFixture
: public testing::Test
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -30,7 +31,6 @@ namespace DisplaySettingsPythonBindingsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
m_app.RegisterComponentDescriptor(AzToolsFramework::DisplaySettingsPythonFuncsHandler::CreateDescriptor());
@ -52,7 +52,7 @@ namespace DisplaySettingsPythonBindingsUnitTests
}
class DisplaySettingsComponentFixture
: public testing::Test
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -60,7 +60,6 @@ namespace DisplaySettingsPythonBindingsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
m_app.RegisterComponentDescriptor(AzToolsFramework::DisplaySettingsComponent::CreateDescriptor());

@ -12,6 +12,7 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
@ -79,7 +80,7 @@ namespace EditorPythonBindingsUnitTests
};
class EditorPythonBindingsFixture
: public testing::Test
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -87,7 +88,6 @@ namespace EditorPythonBindingsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -11,6 +11,7 @@
#include <AzCore/base.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace EditorUtilsTest
{
@ -39,7 +40,7 @@ namespace EditorUtilsTest
class TestWarningAbsorber
: public testing::Test
: public ::UnitTest::ScopedAllocatorSetupFixture
{
};

@ -12,6 +12,7 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
@ -22,7 +23,7 @@ namespace MainWindowPythonBindingsUnitTests
{
class MainWindowPythonBindingsFixture
: public testing::Test
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -30,7 +31,6 @@ namespace MainWindowPythonBindingsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -12,6 +12,7 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
@ -22,7 +23,7 @@ namespace ObjectManagerPythonBindingsUnitTests
{
class ObjectManagerPythonBindingsFixture
: public testing::Test
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -30,7 +31,6 @@ namespace ObjectManagerPythonBindingsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -23,7 +23,7 @@ namespace TerrainFuncsUnitTests
{
class TerrainHoleToolPythonBindingsFixture
: public testing::Test
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -31,7 +31,6 @@ namespace TerrainFuncsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -23,7 +23,7 @@ namespace TerrainFuncsUnitTests
{
class TerrainLayerPythonBindingsFixture
: public testing::Test
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -31,7 +31,6 @@ namespace TerrainFuncsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -22,7 +22,7 @@ namespace TerrainModifyPythonBindingsUnitTests
{
class TerrainModifyPythonBindingsFixture
: public testing::Test
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -30,7 +30,6 @@ namespace TerrainModifyPythonBindingsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -23,7 +23,7 @@ namespace TerrainFuncsUnitTests
{
class TerrainPainterPythonBindingsFixture
: public testing::Test
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -31,7 +31,6 @@ namespace TerrainFuncsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -23,7 +23,7 @@ namespace TerrainFuncsUnitTests
{
class TerrainPythonBindingsFixture
: public testing::Test
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -31,7 +31,6 @@ namespace TerrainFuncsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -23,7 +23,7 @@ namespace TerrainFuncsUnitTests
{
class TerrainTexturePythonBindingsFixture
: public testing::Test
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -31,7 +31,6 @@ namespace TerrainFuncsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -12,6 +12,7 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
@ -22,7 +23,7 @@ namespace TrackViewPythonBindingsUnitTests
{
class TrackViewPythonBindingsFixture
: public testing::Test
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -30,7 +31,6 @@ namespace TrackViewPythonBindingsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
@ -82,7 +82,7 @@ namespace TrackViewPythonBindingsUnitTests
}
class TrackViewComponentFixture
: public testing::Test
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -90,7 +90,6 @@ namespace TrackViewPythonBindingsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
m_app.RegisterComponentDescriptor(AzToolsFramework::TrackViewComponent::CreateDescriptor());

@ -11,6 +11,7 @@
#include <AzCore/base.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
@ -22,7 +23,7 @@ namespace ViewPaneFuncsUnitTests
{
class ViewPanePythonBindingsFixture
: public testing::Test
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -30,7 +31,6 @@ namespace ViewPaneFuncsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -12,6 +12,7 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
@ -22,7 +23,7 @@ namespace ViewportTitleDlgFuncsUnitTests
{
class ViewportTitleDlgPythonBindingsFixture
: public testing::Test
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
AzToolsFramework::ToolsApplication m_app;
@ -30,7 +31,6 @@ namespace ViewportTitleDlgFuncsUnitTests
void SetUp() override
{
AzFramework::Application::Descriptor appDesc;
appDesc.m_enableDrilling = false;
m_app.Start(appDesc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is

@ -13,7 +13,6 @@ set(FILES
EditorCommonAPI.h
ActionOutput.h
ActionOutput.cpp
UiEditorDLLBus.h
DockTitleBarWidget.cpp
DockTitleBarWidget.h
SaveUtilities/AsyncSaveRunner.h

@ -75,17 +75,16 @@ void CImageEx::ReverseUpDown()
}
uint32* pPixData = GetData();
uint32* pReversePix = new uint32[GetWidth() * GetHeight()];
for (int i = GetHeight() - 1, i2 = 0; i >= 0; i--, i2++)
const int height = GetHeight();
const int width = GetWidth();
for (int i = 0; i < height / 2; i++)
{
for (int k = 0; k < GetWidth(); k++)
for (int j = 0; j < width; j++)
{
pReversePix[i2 * GetWidth() + k] = pPixData[i * GetWidth() + k];
AZStd::swap(pPixData[i * width + j], pPixData[(height - 1 - i) * width + j]);
}
}
Attach(pReversePix, GetWidth(), GetHeight());
}
void CImageEx::FillAlpha(unsigned char value)

@ -37,7 +37,7 @@ namespace AZ
using namespace AZ;
// Handle asserts
class TraceDrillerHook
class TestEnvironmentHook
: public AZ::Test::ITestEnvironment
, public UnitTest::TraceBusRedirector
{
@ -57,5 +57,5 @@ public:
}
};
AZ_UNIT_TEST_HOOK(new TraceDrillerHook());
AZ_UNIT_TEST_HOOK(new TestEnvironmentHook());

@ -19,7 +19,6 @@
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/string_view.h>
#include <AzCore/std/typetraits/is_base_of.h>
#include <AzCore/Debug/AssetTracking.h>
#include <AzCore/IO/Streamer/FileRequest.h>
namespace AZ
@ -325,13 +324,13 @@ namespace AZ
T& operator*() const
{
AZ_Assert(m_assetData, "Asset is not loaded");
AZ_Assert(m_assetData, "Asset %s (%s) is not loaded", m_assetId.ToString<AZStd::string>().c_str(), m_assetHint.c_str());
return *Get();
}
T* operator->() const
{
AZ_Assert(m_assetData, "Asset is not loaded");
AZ_Assert(m_assetData, "Asset %s (%s) is not loaded", m_assetId.ToString<AZStd::string>().c_str(), m_assetHint.c_str());
return Get();
}
@ -581,7 +580,6 @@ namespace AZ
template<typename Bus>
using ConnectionPolicy = AssetConnectionPolicy<Bus>;
using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>;
//////////////////////////////////////////////////////////////////////////
virtual ~AssetEvents() {}

@ -10,7 +10,6 @@
#include <AzCore/Asset/AssetManager_private.h>
#include <AzCore/Asset/AssetDataStream.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Debug/AssetTracking.h>
#include <AzCore/IO/IStreamer.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Math/MathUtils.h>
@ -164,8 +163,6 @@ namespace AZ::Data
AZ_PROFILE_SCOPE(AzCore, "AZ::Data::LoadAssetJob::Process: %s",
asset.GetHint().c_str());
AZ_ASSET_ATTACH_TO_SCOPE(this);
if (m_owner->ValidateAndRegisterAssetLoading(asset))
{
LoadAndSignal(asset);
@ -200,7 +197,6 @@ namespace AZ::Data
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(cl_assetLoadDelay));
}
AZ_ASSET_NAMED_SCOPE(asset.GetHint().c_str());
bool loadedSuccessfully = false;
if (!cl_assetLoadError && m_requestState == AZ::IO::IStreamerTypes::RequestStatus::Completed)
@ -982,7 +978,6 @@ namespace AZ::Data
}
AZ_PROFILE_SCOPE(AzCore, "GetAsset: %s", assetInfo.m_relativePath.c_str());
AZ_ASSET_NAMED_SCOPE("GetAsset: %s", assetInfo.m_relativePath.c_str());
AZStd::shared_ptr<AssetDataStream> dataStream;
AssetStreamInfo loadInfo;

@ -48,10 +48,6 @@
#include <AzCore/IO/Path/PathReflect.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Driller/Driller.h>
#include <AzCore/Memory/MemoryDriller.h>
#include <AzCore/Debug/TraceMessagesDriller.h>
#include <AzCore/Debug/EventTraceDriller.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Script/ScriptSystemBus.h>
@ -156,7 +152,6 @@ namespace AZ
m_reservedDebug = 0;
m_recordingMode = Debug::AllocationRecords::RECORD_STACK_IF_NO_FILE_LINE;
m_stackRecordLevels = 5;
m_enableDrilling = false;
m_useOverrunDetection = false;
m_useMalloc = false;
}
@ -328,7 +323,6 @@ namespace AZ
->Field("blockSize", &Descriptor::m_memoryBlocksByteSize)
->Field("reservedOS", &Descriptor::m_reservedOS)
->Field("reservedDebug", &Descriptor::m_reservedDebug)
->Field("enableDrilling", &Descriptor::m_enableDrilling)
->Field("useOverrunDetection", &Descriptor::m_useOverrunDetection)
->Field("useMalloc", &Descriptor::m_useMalloc)
->Field("allocatorRemappings", &Descriptor::m_allocatorRemappings)
@ -367,7 +361,6 @@ namespace AZ
->Attribute(Edit::Attributes::Step, &Descriptor::m_pageSize)
->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_reservedOS, "OS reserved memory", "System memory reserved for OS (used only when 'Allocate all memory at startup' is true)")
->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_reservedDebug, "Memory reserved for debugger", "System memory reserved for Debug allocator, like memory tracking (used only when 'Allocate all memory at startup' is true)")
->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_enableDrilling, "Enable Driller", "Enable Drilling support for the application (ignored in Release builds)")
->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_useOverrunDetection, "Use Overrun Detection", "Use the overrun detection memory manager (only available on some platforms, ignored in Release builds)")
->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_useMalloc, "Use Malloc", "Use malloc for memory allocations (for memory debugging only, ignored in Release builds)")
;
@ -486,9 +479,11 @@ namespace AZ
// Merge Command Line arguments
constexpr bool executeRegDumpCommands = false;
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
// Skip over merging the User Registry in non-debug and profile configurations
SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(*m_settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {});
#endif
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry);

@ -41,7 +41,6 @@ namespace AZ
}
namespace AZ::Debug
{
class DrillerManager;
class LocalFileEventLogger;
}
@ -143,7 +142,6 @@ namespace AZ
AZ::u64 m_reservedDebug; //!< Reserved memory for Debugging (allocation,etc.). Used only when m_grabAllMemory is set to true. (default: 0)
Debug::AllocationRecords::Mode m_recordingMode; //!< When to record stack traces (default: AZ::Debug::AllocationRecords::RECORD_STACK_IF_NO_FILE_LINE)
AZ::u64 m_stackRecordLevels; //!< If stack recording is enabled, how many stack levels to record. (default: 5)
bool m_enableDrilling; //!< True to enabled drilling support for the application. RegisterDrillers will be called. Ignored in release. (default: true)
bool m_useOverrunDetection; //!< True to use the overrun detection memory management scheme. Only available on some platforms; greatly increases memory consumption.
bool m_useMalloc; //!< True to use malloc instead of the internal memory manager. Intended for debugging purposes only.

@ -37,11 +37,6 @@ namespace AZ
class ComponentFactoryInterface;
}
namespace Debug
{
class DrillerManager;
}
struct ApplicationTypeQuery
{
bool IsEditor() const;

@ -16,7 +16,6 @@
#define AZCORE_COMPONENT_TICK_BUS_H
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Debug/AssetTracking.h>
#include <AzCore/std/chrono/chrono.h>
#include <AzCore/std/parallel/mutex.h> // For TickBus thread events.
#include <AzCore/Script/ScriptTimePoint.h>
@ -112,10 +111,6 @@ namespace AZ
AZ_FORCE_INLINE bool operator()(TickEvents* left, TickEvents* right) const { return left->GetTickOrder() < right->GetTickOrder(); }
};
/**
* Enable tick bus to work with the AssetTracking
*/
using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>;
//////////////////////////////////////////////////////////////////////////
/**
@ -217,10 +212,6 @@ namespace AZ
*/
typedef AZStd::mutex EventQueueMutexType;
/**
* Enable tick bus to work with the AssetTracking
*/
using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>;
//////////////////////////////////////////////////////////////////////////
/**

@ -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
*
*/
#pragma once
#include <AzCore/DOM/Backends/JSON/JsonSerializationUtils.h>
#include <AzCore/DOM/DomBackend.h>
#include <AzCore/IO/ByteContainerStream.h>
namespace AZ::Dom
{
//! A DOM backend for serializing and deserializing JSON <=> UTF-8 text
//! \param ParseFlags Controls how deserialized JSON is parsed.
//! \param WriteFormat Controls how serialized JSON is formatted.
template<
Json::ParseFlags ParseFlags = Json::ParseFlags::ParseComments,
Json::OutputFormatting WriteFormat = Json::OutputFormatting::PrettyPrintedJson>
class JsonBackend final : public Backend
{
public:
Visitor::Result ReadFromBuffer(const char* buffer, size_t size, AZ::Dom::Lifetime lifetime, Visitor& visitor) override
{
return Json::VisitSerializedJson<ParseFlags>({ buffer, size }, lifetime, visitor);
}
Visitor::Result ReadFromBufferInPlace(char* buffer, [[maybe_unused]] AZStd::optional<size_t> size, Visitor& visitor) override
{
return Json::VisitSerializedJsonInPlace<ParseFlags>(buffer, visitor);
}
Visitor::Result WriteToBuffer(AZStd::string& buffer, WriteCallback callback)
{
AZ::IO::ByteContainerStream<AZStd::string> stream{ &buffer };
AZStd::unique_ptr<Visitor> visitor = Json::CreateJsonStreamWriter(stream, WriteFormat);
return callback(*visitor);
}
};
} // namespace AZ::Dom

@ -0,0 +1,582 @@
/*
* 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/DOM/Backends/JSON/JsonSerializationUtils.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/IO/TextStreamWriters.h>
#include <AzCore/JSON/filewritestream.h>
#include <AzCore/JSON/memorystream.h>
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/JSON/rapidjson.h>
#include <AzCore/JSON/reader.h>
#include <AzCore/JSON/writer.h>
#include <AzCore/std/containers/stack.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/optional.h>
namespace AZ::Dom::Json
{
//
// class RapidJsonValueWriter
//
RapidJsonValueWriter::RapidJsonValueWriter(rapidjson::Value& outputValue, rapidjson::Value::AllocatorType& allocator)
: m_result(outputValue)
, m_allocator(allocator)
{
}
VisitorFlags RapidJsonValueWriter::GetVisitorFlags() const
{
return VisitorFlags::SupportsRawKeys | VisitorFlags::SupportsArrays | VisitorFlags::SupportsObjects;
}
Visitor::Result RapidJsonValueWriter::Null()
{
CurrentValue().SetNull();
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Bool(bool value)
{
CurrentValue().SetBool(value);
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Int64(AZ::s64 value)
{
CurrentValue().SetInt64(value);
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Uint64(AZ::u64 value)
{
CurrentValue().SetUint64(value);
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Double(double value)
{
CurrentValue().SetDouble(value);
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::String(AZStd::string_view value, Lifetime lifetime)
{
if (lifetime == Lifetime::Temporary)
{
CurrentValue().SetString(value.data(), aznumeric_cast<rapidjson::SizeType>(value.length()), m_allocator);
}
else
{
CurrentValue().SetString(value.data(), aznumeric_cast<rapidjson::SizeType>(value.length()));
}
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::StartObject()
{
CurrentValue().SetObject();
const bool isObject = true;
m_entryStack.emplace_front(isObject, CurrentValue());
return VisitorSuccess();
}
Visitor::Result RapidJsonValueWriter::EndObject(AZ::u64 attributeCount)
{
if (m_entryStack.empty())
{
return VisitorFailure(VisitorErrorCode::InternalError, "EndObject called without a matching BeginObject call");
}
const ValueInfo& frontEntry = m_entryStack.front();
if (!frontEntry.m_isObject)
{
return VisitorFailure(VisitorErrorCode::InternalError, "Expected EndArray and received EndObject instead");
}
if (frontEntry.m_entryCount != attributeCount)
{
return VisitorFailure(
VisitorErrorCode::InternalError,
AZStd::string::format(
"EndObject: Expected %llu attributes but received %llu attributes instead", attributeCount,
frontEntry.m_entryCount));
}
m_entryStack.pop_front();
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Key(AZ::Name key)
{
return RawKey(key.GetStringView(), Lifetime::Persistent);
}
Visitor::Result RapidJsonValueWriter::RawKey(AZStd::string_view key, Lifetime lifetime)
{
AZ_Assert(!m_entryStack.empty(), "Attempmted to push a key with no object");
AZ_Assert(m_entryStack.front().m_isObject, "Attempted to push a key to an array");
if (lifetime == Lifetime::Persistent)
{
m_entryStack.front().m_key.SetString(key.data(), aznumeric_cast<rapidjson::SizeType>(key.size()));
}
else
{
m_entryStack.front().m_key.SetString(key.data(), aznumeric_cast<rapidjson::SizeType>(key.size()), m_allocator);
}
return VisitorSuccess();
}
Visitor::Result RapidJsonValueWriter::StartArray()
{
CurrentValue().SetArray();
const bool isObject = false;
m_entryStack.emplace_front(isObject, CurrentValue());
return VisitorSuccess();
}
Visitor::Result RapidJsonValueWriter::EndArray(AZ::u64 elementCount)
{
if (m_entryStack.empty())
{
return VisitorFailure(VisitorErrorCode::InternalError, "EndArray called without a matching BeginArray call");
}
const ValueInfo& frontEntry = m_entryStack.front();
if (frontEntry.m_isObject)
{
return VisitorFailure(VisitorErrorCode::InternalError, "Expected EndObject and received EndArray instead");
}
if (frontEntry.m_entryCount != elementCount)
{
return VisitorFailure(
VisitorErrorCode::InternalError,
AZStd::string::format(
"EndArray: Expected %llu elements but received %llu elements instead", elementCount, frontEntry.m_entryCount));
}
m_entryStack.pop_front();
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::FinishWrite()
{
if (m_entryStack.empty())
{
return VisitorSuccess();
}
// Retrieve the top value of the stack and replace it with a null value
rapidjson::Value value;
m_entryStack.front().m_value.Swap(value);
ValueInfo& newEntry = m_entryStack.front();
++newEntry.m_entryCount;
if (newEntry.m_key.IsString())
{
newEntry.m_container.AddMember(m_entryStack.front().m_key.Move(), AZStd::move(value), m_allocator);
newEntry.m_key.SetNull();
}
else
{
newEntry.m_container.PushBack(AZStd::move(value), m_allocator);
}
return VisitorSuccess();
}
rapidjson::Value& RapidJsonValueWriter::CurrentValue()
{
if (m_entryStack.empty())
{
return m_result;
}
return m_entryStack.front().m_value;
}
RapidJsonValueWriter::ValueInfo::ValueInfo(bool isObject, rapidjson::Value& container)
: m_isObject(isObject)
, m_container(container)
{
}
//
// class StreamWriter
//
// Visitor that writes to a rapidjson::Writer
template<class Writer>
class StreamWriter : public Visitor
{
public:
StreamWriter(AZ::IO::GenericStream* stream)
: m_streamWriter(stream)
, m_writer(Writer(m_streamWriter))
{
}
VisitorFlags GetVisitorFlags() const override
{
return VisitorFlags::SupportsRawKeys | VisitorFlags::SupportsArrays | VisitorFlags::SupportsObjects;
}
Result Null() override
{
return CheckWrite(m_writer.Null());
}
Result Bool(bool value) override
{
return CheckWrite(m_writer.Bool(value));
}
Result Int64(AZ::s64 value) override
{
return CheckWrite(m_writer.Int64(value));
}
Result Uint64(AZ::u64 value) override
{
return CheckWrite(m_writer.Uint64(value));
}
Result Double(double value) override
{
return CheckWrite(m_writer.Double(value));
}
Result String(AZStd::string_view value, Lifetime lifetime) override
{
const bool shouldCopy = lifetime == Lifetime::Temporary;
return CheckWrite(m_writer.String(value.data(), aznumeric_cast<rapidjson::SizeType>(value.size()), shouldCopy));
}
Result StartObject() override
{
return CheckWrite(m_writer.StartObject());
}
Result EndObject(AZ::u64 attributeCount) override
{
return CheckWrite(m_writer.EndObject(aznumeric_cast<rapidjson::SizeType>(attributeCount)));
}
Result Key(AZ::Name key) override
{
return RawKey(key.GetStringView(), Lifetime::Persistent);
}
Result RawKey(AZStd::string_view key, Lifetime lifetime) override
{
const bool shouldCopy = lifetime == Lifetime::Temporary;
return CheckWrite(m_writer.Key(key.data(), aznumeric_cast<rapidjson::SizeType>(key.size()), shouldCopy));
}
Result StartArray() override
{
return CheckWrite(m_writer.StartArray());
}
Result EndArray(AZ::u64 elementCount) override
{
return CheckWrite(m_writer.EndArray(aznumeric_cast<rapidjson::SizeType>(elementCount)));
}
private:
Result CheckWrite(bool writeSucceeded)
{
if (writeSucceeded)
{
return VisitorSuccess();
}
else
{
return VisitorFailure(VisitorErrorCode::InternalError, "Failed to write JSON");
}
}
AZ::IO::RapidJSONStreamWriter m_streamWriter;
Writer m_writer;
};
//
// struct JsonReadHandler
//
RapidJsonReadHandler::RapidJsonReadHandler(Visitor* visitor, Lifetime stringLifetime)
: m_visitor(visitor)
, m_stringLifetime(stringLifetime)
, m_outcome(AZ::Success())
{
}
bool RapidJsonReadHandler::Null()
{
return CheckResult(m_visitor->Null());
}
bool RapidJsonReadHandler::Bool(bool b)
{
return CheckResult(m_visitor->Bool(b));
}
bool RapidJsonReadHandler::Int(int i)
{
return CheckResult(m_visitor->Int64(aznumeric_cast<AZ::s64>(i)));
}
bool RapidJsonReadHandler::Uint(unsigned i)
{
return CheckResult(m_visitor->Uint64(aznumeric_cast<AZ::u64>(i)));
}
bool RapidJsonReadHandler::Int64(int64_t i)
{
return CheckResult(m_visitor->Int64(i));
}
bool RapidJsonReadHandler::Uint64(uint64_t i)
{
return CheckResult(m_visitor->Uint64(i));
}
bool RapidJsonReadHandler::Double(double d)
{
return CheckResult(m_visitor->Double(d));
}
bool RapidJsonReadHandler::RawNumber(
[[maybe_unused]] const char* str, [[maybe_unused]] rapidjson::SizeType length, [[maybe_unused]] bool copy)
{
AZ_Assert(false, "Raw numbers are unsupported in the rapidjson DOM backend");
return false;
}
bool RapidJsonReadHandler::String(const char* str, rapidjson::SizeType length, bool copy)
{
const Lifetime lifetime = copy ? m_stringLifetime : Lifetime::Temporary;
return CheckResult(m_visitor->String(AZStd::string_view(str, length), lifetime));
}
bool RapidJsonReadHandler::StartObject()
{
return CheckResult(m_visitor->StartObject());
}
bool RapidJsonReadHandler::Key(const char* str, rapidjson::SizeType length, [[maybe_unused]] bool copy)
{
AZStd::string_view key = AZStd::string_view(str, length);
if (!m_visitor->SupportsRawKeys())
{
m_visitor->Key(AZ::Name(key));
}
const Lifetime lifetime = copy ? m_stringLifetime : Lifetime::Temporary;
return CheckResult(m_visitor->RawKey(key, lifetime));
}
bool RapidJsonReadHandler::EndObject([[maybe_unused]] rapidjson::SizeType memberCount)
{
return CheckResult(m_visitor->EndObject(memberCount));
}
bool RapidJsonReadHandler::StartArray()
{
return CheckResult(m_visitor->StartArray());
}
bool RapidJsonReadHandler::EndArray([[maybe_unused]] rapidjson::SizeType elementCount)
{
return CheckResult(m_visitor->EndArray(elementCount));
}
Visitor::Result&& RapidJsonReadHandler::TakeOutcome()
{
return AZStd::move(m_outcome);
}
bool RapidJsonReadHandler::CheckResult(Visitor::Result result)
{
if (result.IsSuccess())
{
return true;
}
else
{
m_outcome = AZStd::move(result);
return false;
}
}
//
// Serialized JSON util functions
//
AZStd::unique_ptr<Visitor> CreateJsonStreamWriter(AZ::IO::GenericStream& stream, OutputFormatting format)
{
if (format == OutputFormatting::MinifiedJson)
{
using WriterType = rapidjson::Writer<AZ::IO::RapidJSONStreamWriter>;
return AZStd::make_unique<StreamWriter<WriterType>>(&stream);
}
else
{
using WriterType = rapidjson::PrettyWriter<AZ::IO::RapidJSONStreamWriter>;
return AZStd::make_unique<StreamWriter<WriterType>>(&stream);
}
}
//
// In-memory rapidjson util functions
//
AZ::Outcome<rapidjson::Document, AZStd::string> WriteToRapidJsonDocument(Backend::WriteCallback writeCallback)
{
rapidjson::Document document;
RapidJsonValueWriter writer(document, document.GetAllocator());
auto result = writeCallback(writer);
if (!result.IsSuccess())
{
return AZ::Failure(result.TakeError().FormatVisitorErrorMessage());
}
return AZ::Success(AZStd::move(document));
}
Visitor::Result WriteToRapidJsonValue(
rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator, Backend::WriteCallback writeCallback)
{
RapidJsonValueWriter writer(value, allocator);
return writeCallback(writer);
}
Visitor::Result VisitRapidJsonValue(const rapidjson::Value& value, Visitor& visitor, Lifetime lifetime)
{
struct EndArrayMarker
{
};
struct EndObjectMarker
{
};
// Processing stack consists of values comprised of one of a:
// - rapidjson::Value to process
// - EndArrayMarker or EndObjectMarker denoting the end of an array or object
// - string denoting a key at the beginning of a key/value pair
using Entry = AZStd::variant<const rapidjson::Value*, EndArrayMarker, EndObjectMarker, AZStd::string_view>;
AZStd::stack<Entry> entryStack;
AZStd::stack<u64> entryCountStack;
entryStack.push(&value);
while (!entryStack.empty())
{
const Entry currentEntry = entryStack.top();
entryStack.pop();
Visitor::Result result = AZ::Success();
AZStd::visit(
[&visitor, &entryStack, &entryCountStack, &result, lifetime](auto&& arg)
{
using Alternative = AZStd::decay_t<decltype(arg)>;
if constexpr (AZStd::is_same_v<Alternative, const rapidjson::Value*>)
{
const rapidjson::Value& currentValue = *arg;
if (!entryCountStack.empty())
{
++entryCountStack.top();
}
switch (currentValue.GetType())
{
case rapidjson::kNullType:
result = visitor.Null();
break;
case rapidjson::kFalseType:
result = visitor.Bool(false);
break;
case rapidjson::kTrueType:
result = visitor.Bool(true);
break;
case rapidjson::kObjectType:
entryStack.push(EndObjectMarker{});
entryCountStack.push(0);
result = visitor.StartObject();
for (auto it = currentValue.MemberEnd(); it != currentValue.MemberBegin(); --it)
{
auto entry = (it - 1);
const AZStd::string_view key(
entry->name.GetString(), aznumeric_cast<size_t>(entry->name.GetStringLength()));
entryStack.push(&entry->value);
entryStack.push(key);
}
break;
case rapidjson::kArrayType:
entryStack.push(EndArrayMarker{});
entryCountStack.push(0);
result = visitor.StartArray();
for (auto it = currentValue.End(); it != currentValue.Begin(); --it)
{
auto entry = (it - 1);
entryStack.push(entry);
}
break;
case rapidjson::kStringType:
result = visitor.String(
AZStd::string_view(currentValue.GetString(), aznumeric_cast<size_t>(currentValue.GetStringLength())),
lifetime);
break;
case rapidjson::kNumberType:
if (currentValue.IsFloat() || currentValue.IsDouble())
{
result = visitor.Double(currentValue.GetDouble());
}
else if (currentValue.IsInt64() || currentValue.IsInt())
{
result = visitor.Int64(currentValue.GetInt64());
}
else
{
result = visitor.Uint64(currentValue.GetUint64());
}
break;
default:
result = AZ::Failure(VisitorError(VisitorErrorCode::InvalidData, "Value with invalid type specified"));
}
}
else if constexpr (AZStd::is_same_v<Alternative, EndArrayMarker>)
{
result = visitor.EndArray(entryCountStack.top());
entryCountStack.pop();
}
else if constexpr (AZStd::is_same_v<Alternative, EndObjectMarker>)
{
result = visitor.EndObject(entryCountStack.top());
entryCountStack.pop();
}
else if constexpr (AZStd::is_same_v<Alternative, AZStd::string_view>)
{
if (visitor.SupportsRawKeys())
{
visitor.RawKey(arg, lifetime);
}
else
{
visitor.Key(AZ::Name(arg));
}
}
},
currentEntry);
if (!result.IsSuccess())
{
return result;
}
}
return AZ::Success();
}
} // namespace AZ::Dom::Json

@ -0,0 +1,256 @@
/*
* 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 <AzCore/DOM/DomBackend.h>
#include <AzCore/DOM/DomVisitor.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/JSON/document.h>
#include <AzCore/std/containers/deque.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ::Dom::Json
{
//! Specifies how JSON should be formatted when serialized.
enum class OutputFormatting
{
MinifiedJson, //!< Formats JSON in compact minified form, focusing on minimizing output size.
PrettyPrintedJson, //!< Formats JSON in a pretty printed form, focusing on legibility to readers.
};
//! Specifies parsing behavior when deserializing JSON.
enum class ParseFlags : int
{
Null = 0,
StopWhenDone = rapidjson::kParseStopWhenDoneFlag,
FullFloatingPointPrecision = rapidjson::kParseFullPrecisionFlag,
ParseComments = rapidjson::kParseCommentsFlag,
ParseNumbersAsStrings = rapidjson::kParseNumbersAsStringsFlag,
ParseTrailingCommas = rapidjson::kParseTrailingCommasFlag,
ParseNanAndInfinity = rapidjson::kParseNanAndInfFlag,
ParseEscapedApostrophies = rapidjson::kParseEscapedApostropheFlag,
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(ParseFlags);
//! Visitor that feeds into a rapidjson::Value
class RapidJsonValueWriter final : public Visitor
{
public:
RapidJsonValueWriter(rapidjson::Value& outputValue, rapidjson::Value::AllocatorType& allocator);
VisitorFlags GetVisitorFlags() const override;
Result Null() override;
Result Bool(bool value) override;
Result Int64(AZ::s64 value) override;
Result Uint64(AZ::u64 value) override;
Result Double(double value) override;
Result String(AZStd::string_view value, Lifetime lifetime) override;
Result StartObject() override;
Result EndObject(AZ::u64 attributeCount) override;
Result Key(AZ::Name key) override;
Result RawKey(AZStd::string_view key, Lifetime lifetime) override;
Result StartArray() override;
Result EndArray(AZ::u64 elementCount) override;
private:
Result FinishWrite();
rapidjson::Value& CurrentValue();
struct ValueInfo
{
ValueInfo(bool isObject, rapidjson::Value& container);
rapidjson::Value m_key;
rapidjson::Value m_value;
rapidjson::Value& m_container;
AZ::u64 m_entryCount = 0;
bool m_isObject;
};
rapidjson::Value& m_result;
rapidjson::Value::AllocatorType& m_allocator;
AZStd::deque<ValueInfo> m_entryStack;
};
//! Handler for a rapidjson::Reader that translates reads into an AZ::Dom::Visitor
struct RapidJsonReadHandler
{
public:
RapidJsonReadHandler(Visitor* visitor, Lifetime stringLifetime);
bool Null();
bool Bool(bool b);
bool Int(int i);
bool Uint(unsigned i);
bool Int64(int64_t i);
bool Uint64(uint64_t i);
bool Double(double d);
bool RawNumber(const char* str, rapidjson::SizeType length, bool copy);
bool String(const char* str, rapidjson::SizeType length, bool copy);
bool StartObject();
bool Key(const char* str, rapidjson::SizeType length, bool copy);
bool EndObject(rapidjson::SizeType memberCount);
bool StartArray();
bool EndArray(rapidjson::SizeType elementCount);
Visitor::Result&& TakeOutcome();
private:
bool CheckResult(Visitor::Result result);
Visitor::Result m_outcome;
Visitor* m_visitor;
Lifetime m_stringLifetime;
};
//! rapidjson stream wrapper for AZStd::string suitable for in-situ parsing
//! Faster than rapidjson::MemoryStream for reading from AZStd::string / AZStd::string_view (because it requires a null terminator)
//! \note This needs to be inlined for performance reasons.
struct NullDelimitedStringStream
{
using Ch = char; //<! Denotes the string character storage type for rapidjson
AZ_FORCE_INLINE NullDelimitedStringStream(char* buffer)
{
m_cursor = buffer;
m_begin = m_cursor;
}
AZ_FORCE_INLINE NullDelimitedStringStream(AZStd::string_view buffer)
{
// rapidjson won't actually call PutBegin or Put unless kParseInSituFlag is set, so this is safe
m_cursor = const_cast<char*>(buffer.data());
m_begin = m_cursor;
}
AZ_FORCE_INLINE char Peek() const
{
return *m_cursor;
}
AZ_FORCE_INLINE char Take()
{
return *m_cursor++;
}
AZ_FORCE_INLINE size_t Tell() const
{
return static_cast<size_t>(m_cursor - m_begin);
}
AZ_FORCE_INLINE char* PutBegin()
{
m_write = m_cursor;
return m_cursor;
}
AZ_FORCE_INLINE void Put(char c)
{
(*m_write++) = c;
}
AZ_FORCE_INLINE void Flush()
{
}
AZ_FORCE_INLINE size_t PutEnd(char* begin)
{
return m_write - begin;
}
AZ_FORCE_INLINE const char* Peek4() const
{
AZ_Assert(false, "Not implemented, encoding is hard-coded to UTF-8");
return m_cursor;
}
char* m_cursor; //!< Current read position.
char* m_write; //!< Current write position.
const char* m_begin; //!< Head of string.
};
//! Creates a Visitor that will write serialized JSON to the specified stream.
//! \param stream The stream the visitor will write to.
//! \param format The format to write in.
//! \return A Visitor that will write to stream when visited.
AZStd::unique_ptr<Visitor> CreateJsonStreamWriter(
AZ::IO::GenericStream& stream, OutputFormatting format = OutputFormatting::PrettyPrintedJson);
//! Reads serialized JSON from a string and applies it to a visitor.
//! \param buffer The UTF-8 serialized JSON to read.
//! \param lifetime Specifies the lifetime of the specified buffer. If the string specified by buffer might be deallocated,
//! ensure Lifetime::Temporary is specified.
//! \param visitor The visitor to visit with the JSON buffer's contents.
//! \param parseFlags (template) Settings for adjusting parser behavior.
//! \return The aggregate result specifying whether the visitor operations were successful.
template<ParseFlags parseFlags = ParseFlags::ParseComments>
Visitor::Result VisitSerializedJson(AZStd::string_view buffer, Lifetime lifetime, Visitor& visitor);
//! Reads serialized JSON from a string in-place and applies it to a visitor.
//! \param buffer The UTF-8 serialized JSON to read. This buffer will be modified as part of the deserialization process to
//! apply null terminators.
//! \param visitor The visitor to visit with the JSON buffer's contents. The strings provided to the visitor will only
//! be valid for the lifetime of buffer.
//! \param parseFlags (template) Settings for adjusting parser behavior.
//! \return The aggregate result specifying whether the visitor operations were successful.
template<ParseFlags parseFlags = ParseFlags::ParseComments>
Visitor::Result VisitSerializedJsonInPlace(char* buffer, Visitor& visitor);
//! Takes a visitor specified by a callback and produces a rapidjson::Document.
//! \param writeCallback A callback specifying a visitor to accept to build the resulting document.
//! \return An outcome with either the rapidjson::Document or an error message.
AZ::Outcome<rapidjson::Document, AZStd::string> WriteToRapidJsonDocument(Backend::WriteCallback writeCallback);
//! Takes a visitor specified by a callback and reads them into a rapidjson::Value.
//! \param value The value to read into, its contents will be overridden.
//! \param allocator The allocator to use when performing rapidjson allocations (generally provded by the rapidjson::Document).
//! \param writeCallback A callback specifying a visitor to accept to build the resulting document.
//! \return An outcome with either the rapidjson::Document or an error message.
Visitor::Result WriteToRapidJsonValue(
rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator, Backend::WriteCallback writeCallback);
//! Accepts a visitor with the contents of a rapidjson::Value.
//! \param value The rapidjson::Value to apply to visitor.
//! \param visitor The visitor to receive the contents of value.
//! \param lifetime The lifetime to specify for visiting strings. If the rapidjson::Value might be destroyed or changed
//! before the visitor is finished using these values, Lifetime::Temporary should be specified.
//! \return The aggregate result specifying whether the visitor operations were successful.
Visitor::Result VisitRapidJsonValue(const rapidjson::Value& value, Visitor& visitor, Lifetime lifetime);
template<ParseFlags parseFlags>
Visitor::Result VisitSerializedJson(AZStd::string_view buffer, Lifetime lifetime, Visitor& visitor)
{
rapidjson::Reader reader;
RapidJsonReadHandler handler(&visitor, lifetime);
// If the string is null terminated, we can use the faster AzStringStream path - otherwise we fall back on rapidjson::MemoryStream
if (buffer.data()[buffer.size()] == '\0')
{
NullDelimitedStringStream stream(buffer);
reader.Parse<aznumeric_cast<unsigned>(parseFlags)>(stream, handler);
}
else
{
rapidjson::MemoryStream stream(buffer.data(), buffer.size());
reader.Parse<aznumeric_cast<unsigned>(parseFlags)>(stream, handler);
}
return handler.TakeOutcome();
}
template<ParseFlags parseFlags>
Visitor::Result VisitSerializedJsonInPlace(char* buffer, Visitor& visitor)
{
rapidjson::Reader reader;
NullDelimitedStringStream stream(buffer);
RapidJsonReadHandler handler(&visitor, Lifetime::Persistent);
reader.Parse<aznumeric_cast<unsigned>(parseFlags) | rapidjson::kParseInsituFlag>(stream, handler);
return handler.TakeOutcome();
}
} // namespace AZ::Dom::Json

@ -0,0 +1,17 @@
/*
* 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/DOM/DomBackend.h>
namespace AZ::Dom
{
Visitor::Result Backend::ReadFromBufferInPlace(char* buffer, AZStd::optional<size_t> size, Visitor& visitor)
{
return ReadFromBuffer(buffer, size.value_or(strlen(buffer)), AZ::Dom::Lifetime::Persistent, visitor);
}
} // namespace AZ::Dom

@ -0,0 +1,44 @@
/*
* 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 <AzCore/DOM/DomVisitor.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/optional.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ::Dom
{
//! Backends are registered centrally and used to transition DOM formats to and from a textual format.
class Backend
{
public:
virtual ~Backend() = default;
//! Attempt to read this format from the given buffer into the target Visitor.
virtual Visitor::Result ReadFromBuffer(
const char* buffer, size_t size, AZ::Dom::Lifetime lifetime, Visitor& visitor) = 0;
//! Attempt to read this format from a mutable string into the target Visitor. This enables some backends to
//! parse without making additional string allocations.
//! This string must be null terminated.
//! This string may be modified and read in place without being copied, so when calling this please ensure that:
//! - The string won't be deallocated until the visitor no longer needs the values and
//! - The string is safe to modify in place.
//! The base implementation simply calls ReadFromBuffer.
virtual Visitor::Result ReadFromBufferInPlace(char* buffer, AZStd::optional<size_t> size, Visitor& visitor);
//! A callback that accepts a Visitor, making DOM calls to inform the serializer, and returns an
//! aggregate error code to indicate whether or not the operation succeeded.
using WriteCallback = AZStd::function<Visitor::Result(Visitor&)>;
//! Attempt to write a value to the specified string using a write callback.
virtual Visitor::Result WriteToBuffer(AZStd::string& buffer, WriteCallback callback) = 0;
};
} // namespace AZ::Dom

@ -0,0 +1,24 @@
/*
* 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/DOM/DomUtils.h>
#include <AzCore/IO/ByteContainerStream.h>
namespace AZ::Dom::Utils
{
Visitor::Result ReadFromString(Backend& backend, AZStd::string_view string, AZ::Dom::Lifetime lifetime, Visitor& visitor)
{
return backend.ReadFromBuffer(string.data(), string.length(), lifetime, visitor);
}
Visitor::Result ReadFromStringInPlace(Backend& backend, AZStd::string& string, Visitor& visitor)
{
return backend.ReadFromBufferInPlace(string.data(), string.size(), visitor);
}
}

@ -0,0 +1,17 @@
/*
* 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 <AzCore/DOM/DomBackend.h>
namespace AZ::Dom::Utils
{
Visitor::Result ReadFromString(Backend& backend, AZStd::string_view string, AZ::Dom::Lifetime lifetime, Visitor& visitor);
Visitor::Result ReadFromStringInPlace(Backend& backend, AZStd::string& string, Visitor& visitor);
} // namespace AZ::Dom::Utils

@ -8,7 +8,7 @@
#include <AzCore/DOM/DomVisitor.h>
namespace AZ::DOM
namespace AZ::Dom
{
const char* VisitorError::CodeToString(VisitorErrorCode code)
{
@ -236,4 +236,4 @@ namespace AZ::DOM
{
return (GetVisitorFlags() & VisitorFlags::SupportsOpaqueValues) != VisitorFlags::Null;
}
} // namespace AZ::DOM
} // namespace AZ::Dom

@ -13,7 +13,7 @@
#include <AzCore/std/any.h>
#include <AzCore/std/string/string.h>
namespace AZ::DOM
namespace AZ::Dom
{
//
// Lifetime enum
@ -75,7 +75,7 @@ namespace AZ::DOM
};
//! A type alias for opaque DOM types that aren't meant to be serializable.
//! /see VisitorInterface::OpaqueValue
//! \see VisitorInterface::OpaqueValue
using OpaqueType = AZStd::any;
//
@ -116,7 +116,7 @@ namespace AZ::DOM
//! - \ref Double: 64 bit double precision float
//! - \ref Null: sentinel "empty" type with no value representation
//! - \ref String: UTF8 encoded string
//! - \ref Object: an ordered container of key/value pairs where keys are AZ::Names and values may be any DOM type
//! - \ref Object: an ordered container of key/value pairs where keys are \ref AZ::Name and values may be any DOM type
//! (including Object)
//! - \ref Array: an ordered container of values, in which values are any DOM value type (including Array)
//! - \ref Node: a container
@ -144,17 +144,17 @@ namespace AZ::DOM
//! Raw (\see VisitorFlags::SupportsRawValues) and opaque values (\see VisitorFlags::SupportsOpaqueValues)
//! are disallowed by default, as their handling is intended to be implementation-specific.
virtual VisitorFlags GetVisitorFlags() const;
//! /see VisitorFlags::SupportsRawValues
//! \see VisitorFlags::SupportsRawValues
bool SupportsRawValues() const;
//! /see VisitorFlags::SupportsRawKeys
//! \see VisitorFlags::SupportsRawKeys
bool SupportsRawKeys() const;
//! /see VisitorFlags::SupportsObjects
//! \see VisitorFlags::SupportsObjects
bool SupportsObjects() const;
//! /see VisitorFlags::SupportsArrays
//! \see VisitorFlags::SupportsArrays
bool SupportsArrays() const;
//! /see VisitorFlags::SupportsNodes
//! \see VisitorFlags::SupportsNodes
bool SupportsNodes() const;
//! /see VisitorFlags::SupportsOpaqueValues
//! \see VisitorFlags::SupportsOpaqueValues
bool SupportsOpaqueValues() const;
//! Operates on an empty null value.
@ -231,7 +231,8 @@ namespace AZ::DOM
static Result VisitorFailure(VisitorErrorCode code, AZStd::string additionalInfo);
//! Helper method, constructs a failure \ref Result with the specified error.
static Result VisitorFailure(VisitorError error);
//! Helper method, constructs a success \ref Result.
static Result VisitorSuccess();
};
} // namespace AZ::DOM
} // namespace AZ::Dom

@ -1,326 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "AssetTracking.h"
#include <AzCore/Debug/AssetTrackingTypes.h>
#include <AzCore/Memory/AllocatorManager.h>
#include <AzCore/Memory/HphaSchema.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/smart_ptr/make_shared.h>
namespace AZ::Debug
{
namespace
{
struct AssetTreeNode;
// Per-thread data that needs to be stored.
struct ThreadData
{
AZStd::vector<AssetTreeNodeBase*, AZStdAssetTrackingAllocator> m_currentAssetStack;
};
// Access thread data through a virtual function to ensure that the same thread-local data is being shared across DLLs.
// Otherwise, the thread_local variables are replicated across DLLs that link the AzCore library, and you'll get a
// different version in each module.
class ThreadDataProvider
{
public:
virtual ThreadData& GetThreadData() = 0;
};
}
class AssetTrackingImpl final :
public ThreadDataProvider
{
public:
AZ_TYPE_INFO(AssetTrackingImpl, "{01E2A099-3523-40BE-80E0-E0ADD861BEE1}");
AZ_CLASS_ALLOCATOR(AssetTrackingImpl, OSAllocator, 0);
AssetTrackingImpl(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable);
~AssetTrackingImpl();
void AssetBegin(const char* id, const char* file, int line);
void AssetAttach(void* otherAllocation, const char* file, int line);
void AssetEnd();
ThreadData& GetThreadData() override;
private:
static EnvironmentVariable<AssetTrackingImpl*>& GetEnvironmentVariable();
static AssetTrackingImpl* GetSharedInstance();
static ThreadData& GetSharedThreadData();
using PrimaryAssets = AZStd::unordered_map<AssetTrackingId, AssetPrimaryInfo, AZStd::hash<AssetTrackingId>, AZStd::equal_to<AssetTrackingId>, AZStdAssetTrackingAllocator>;
using ThreadData = ThreadData;
using mutex_type = AZStd::mutex;
using lock_type = AZStd::lock_guard<mutex_type>;
mutex_type m_mutex;
PrimaryAssets m_primaryAssets;
AssetTreeNodeBase* m_assetRoot = nullptr;
AssetAllocationTableBase* m_allocationTable = nullptr;
bool m_performingAnalysis = false;
friend class AssetTracking;
friend class AssetTracking::Scope;
};
///////////////////////////////////////////////////////////////////////////////
// AssetTrackingImpl methods
///////////////////////////////////////////////////////////////////////////////
AssetTrackingImpl::AssetTrackingImpl(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable) :
m_assetRoot(&assetTree->GetRoot()),
m_allocationTable(allocationTable)
{
AZ_Assert(!GetSharedInstance(), "Only one AssetTrackingImpl can exist!");
GetEnvironmentVariable().Set(this);
AllocatorManager::Instance().EnterProfilingMode();
}
AssetTrackingImpl::~AssetTrackingImpl()
{
AllocatorManager::Instance().ExitProfilingMode();
GetEnvironmentVariable().Reset();
}
void AssetTrackingImpl::AssetBegin(const char* id, const char* file, int line)
{
// In the future it may be desirable to organize assets based on where in code the asset was entered into.
// For now these are ignored.
AZ_UNUSED(file);
AZ_UNUSED(line);
using namespace Internal;
AssetTrackingId assetId(id);
auto& threadData = GetSharedThreadData();
AssetTreeNodeBase* parentAsset = threadData.m_currentAssetStack.empty() ? nullptr : threadData.m_currentAssetStack.back();
AssetTreeNodeBase* childAsset;
AssetPrimaryInfo* assetPrimaryInfo;
if (!parentAsset)
{
parentAsset = m_assetRoot;
}
{
lock_type lock(m_mutex);
// Locate or create the primary record for this asset
auto primaryItr = m_primaryAssets.find(assetId);
if (primaryItr != m_primaryAssets.end())
{
assetPrimaryInfo = &primaryItr->second;
}
else
{
auto insertResult = m_primaryAssets.emplace(assetId, AssetPrimaryInfo());
assetPrimaryInfo = &insertResult.first->second;
assetPrimaryInfo->m_id = &insertResult.first->first;
}
// Add this asset to the stack for this thread's context
childAsset = parentAsset->FindOrAddChild(assetId, assetPrimaryInfo);
}
threadData.m_currentAssetStack.push_back(childAsset);
}
void AssetTrackingImpl::AssetAttach(void* otherAllocation, const char* file, int line)
{
AZ_UNUSED(file);
AZ_UNUSED(line);
using namespace Internal;
AssetTreeNodeBase* assetInfo = m_allocationTable->FindAllocation(otherAllocation);
// We will push back a nullptr if there is no asset, this is necessary to balance the call to AssetEnd()
GetSharedThreadData().m_currentAssetStack.push_back(assetInfo);
}
void AssetTrackingImpl::AssetEnd()
{
AZ_Assert(!GetSharedThreadData().m_currentAssetStack.empty(), "AssetEnd() called without matching AssetBegin() or AssetAttach. Use the AZ_ASSET_NAMED_SCOPE and AZ_ASSET_ATTACH_TO_SCOPE macros to avoid this!");
GetSharedThreadData().m_currentAssetStack.pop_back();
}
AssetTrackingImpl* AssetTrackingImpl::GetSharedInstance()
{
auto environmentVariable = GetEnvironmentVariable();
if(environmentVariable)
{
return *environmentVariable;
}
return nullptr;
}
ThreadData& AssetTrackingImpl::GetSharedThreadData()
{
// Cast to the base type so our virtual call doesn't get optimized away. We require GetThreadData() to be executed in the same DLL every time.
return static_cast<ThreadDataProvider*>(GetSharedInstance())->GetThreadData();
}
AssetTrackingImpl::ThreadData& AssetTrackingImpl::GetThreadData()
{
static thread_local ThreadData* data = nullptr;
static thread_local typename AZStd::aligned_storage_t<sizeof(ThreadData), alignof(ThreadData)> storage;
if (!data)
{
data = new (&storage) ThreadData;
}
return *data;
}
EnvironmentVariable<AssetTrackingImpl*>& AssetTrackingImpl::GetEnvironmentVariable()
{
static EnvironmentVariable<AssetTrackingImpl*> assetTrackingImpl = Environment::CreateVariable<AssetTrackingImpl*>(AzTypeInfo<AssetTrackingImpl*>::Name());
return assetTrackingImpl;
}
///////////////////////////////////////////////////////////////////////////////
// AssetTracking::Scope functions
///////////////////////////////////////////////////////////////////////////////
AssetTracking::Scope AssetTracking::Scope::ScopeFromAssetId(const char* file, int line, const char* fmt, ...)
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
static const int BUFFER_SIZE = 1024;
char buffer[BUFFER_SIZE];
va_list args;
va_start(args, fmt);
azvsnprintf(buffer, BUFFER_SIZE, fmt, args);
va_end(args);
impl->AssetBegin(buffer, file, line);
}
return Scope();
}
AssetTracking::Scope AssetTracking::Scope::ScopeFromAttachment(void* attachTo, const char* file, int line)
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
impl->AssetAttach(attachTo, file, line);
}
return Scope();
}
AssetTracking::Scope::~Scope()
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
impl->AssetEnd();
}
}
AssetTracking::Scope::Scope()
{
}
///////////////////////////////////////////////////////////////////////////////
// AssetTracking functions
///////////////////////////////////////////////////////////////////////////////
void AssetTracking::EnterScopeByAssetId(const char* file, int line, const char* fmt, ...)
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
static const int BUFFER_SIZE = 1024;
char buffer[BUFFER_SIZE];
va_list args;
va_start(args, fmt);
azvsnprintf(buffer, BUFFER_SIZE, fmt, args);
va_end(args);
impl->AssetBegin(buffer, file, line);
}
}
void AssetTracking::EnterScopeByAttachment(void* attachTo, const char* file, int line)
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
impl->AssetAttach(attachTo, file, line);
}
}
void AssetTracking::ExitScope()
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
impl->AssetEnd();
}
}
const char* AssetTracking::GetDebugScope()
{
// Output debug information about the current asset scope in the current thread.
// Do not use in production code.
#ifndef RELEASE
static const int BUFFER_SIZE = 1024;
static char buffer[BUFFER_SIZE];
const auto& assetStack = AssetTrackingImpl::GetSharedInstance()->GetThreadData().m_currentAssetStack;
if (assetStack.empty())
{
azsnprintf(buffer, BUFFER_SIZE, "<none>");
}
else
{
char* pos = buffer;
for (auto itr = assetStack.rbegin(); itr != assetStack.rend(); ++itr)
{
pos += azsnprintf(pos, BUFFER_SIZE - (pos - buffer), "%s\n", (*itr)->GetAssetPrimaryInfo()->m_id->m_id.c_str());
if (pos >= buffer + BUFFER_SIZE)
{
break;
}
}
}
return buffer;
#else
return "";
#endif
}
AssetTracking::AssetTracking(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable)
{
m_impl.reset(aznew AssetTrackingImpl(assetTree, allocationTable));
}
AssetTracking::~AssetTracking()
{
}
AssetTreeNodeBase* AssetTracking::GetCurrentThreadAsset() const
{
const auto& assetStack = m_impl->GetThreadData().m_currentAssetStack;
AssetTreeNodeBase* result = assetStack.empty() ? nullptr : assetStack.back();
return result;
}
} // namespace AzFramework

@ -1,131 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/EBus/Policies.h>
#ifndef AZ_TRACK_ASSET_SCOPES
// You may manually uncomment this to enable asset tracking.
//# define AZ_TRACK_ASSET_SCOPES
#endif
#if !defined(AZ_TRACK_ASSET_SCOPES)
// Default to enabling asset tracking when memory tracking is enabled
# define AZ_TRACK_ASSET_SCOPES
#endif
#ifdef AZ_TRACK_ASSET_SCOPES
#define AZ_ASSET_SCOPE_VARIABLE_NAME(line) AZ_JOIN(_az_assettracking_scope_, line)
///////////////////////////////////////////////////////////////////////////////
// Preferred macros to use at the top of a scope you want to to track asset memory for.
///////////////////////////////////////////////////////////////////////////////
// Creates a new scope with a name, usually the name of an asset being loaded. (This may be a format-string, e.g. "Foo: %s", bar.c_str())
# define AZ_ASSET_NAMED_SCOPE(...) AZ::Debug::AssetTracking::Scope AZ_ASSET_SCOPE_VARIABLE_NAME(__LINE__) (AZ::Debug::AssetTracking::Scope::ScopeFromAssetId(__FILE__, __LINE__, __VA_ARGS__))
// Attempts to enter an existing scope that already owns some other allocation.
# define AZ_ASSET_ATTACH_TO_SCOPE(other) AZ::Debug::AssetTracking::Scope AZ_ASSET_SCOPE_VARIABLE_NAME(__LINE__) (AZ::Debug::AssetTracking::Scope::ScopeFromAttachment((other), __FILE__, __LINE__))
///////////////////////////////////////////////////////////////////////////////
// Optional macros to manually enter and exit a scope.
// It is the responsibility of the user to make sure every call to AZ_ASSET_ENTER_SCOPE_* is matched by a corresponding call to AZ_ASSET_EXIT_SCOPE.
///////////////////////////////////////////////////////////////////////////////
# define AZ_ASSET_ENTER_SCOPE_BY_ASSET_ID(...) AZ::Debug::AssetTracking::EnterScopeByAssetId(__FILE__, __LINE__, __VA_ARGS__)
# define AZ_ASSET_ENTER_SCOPE_BY_ATTACHMENT(other) AZ::Debug::AssetTracking::EnterScopeByAttachment((other), __FILE__, __LINE__)
# define AZ_ASSET_EXIT_SCOPE AZ::Debug::AssetTracking::ExitScope()
#else
# define AZ_ASSET_NAMED_SCOPE(...) (void)0
# define AZ_ASSET_ATTACH_TO_SCOPE(other) (void)0
# define AZ_ASSET_ENTER_SCOPE_BY_ASSET_ID(...) (void)0
# define AZ_ASSET_ENTER_SCOPE_BY_ATTACHMENT(other) (void)0
# define AZ_ASSET_EXIT_SCOPE (void)0
#endif
namespace AZ
{
class ReflectContext;
namespace Debug
{
class AssetTrackingImpl;
class AssetTreeBase;
class AssetTreeNodeBase;
class AssetAllocationTableBase;
class AssetTracking
{
public:
AZ_TYPE_INFO(AssetTracking, "{D4335180-09A2-415A-8B50-9B734E7CE1E6}");
AZ_CLASS_ALLOCATOR(AssetTracking, OSAllocator, 0);
// Provide RAII method for entering and exiting scopes.
// Generally you will want to use the macros at the top of this file rather than instantiating this object directly.
class Scope
{
public:
static Scope ScopeFromAssetId(const char* file, int line, const char* fmt, ...);
static Scope ScopeFromAttachment(void* attachTo, const char* file, int line);
Scope(Scope&&) = default;
~Scope();
private:
Scope();
};
// Generally you will want to use the macros at the top of this file rather than calling these functions directly.
static void EnterScopeByAssetId(const char* file, int line, const char* fmt, ...);
static void EnterScopeByAttachment(void* attachTo, const char* file, int line);
static void ExitScope();
static const char* GetDebugScope();
AssetTracking(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable);
~AssetTracking();
AssetTreeNodeBase* GetCurrentThreadAsset() const;
private:
AZStd::unique_ptr<AssetTrackingImpl> m_impl;
};
// An EBus processing policy that attempts to attach to an existing scope before calling a handler.
//
// Use this on EBuses where you want the callees to track asset memory during their event handlers.
// This will work so long as the callees were themselves allocated inside an existing asset scope.
//
// May be added to an existing EBus with the following code:
// using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy;
//
template<typename Parent = EBusEventProcessingPolicy>
struct AssetTrackingEventProcessingPolicy
{
template<class Results, class Function, class Interface, class... InputArgs>
static void CallResult(Results& results, Function&& func, Interface&& iface, InputArgs&&... args)
{
AZ_ASSET_ATTACH_TO_SCOPE(iface);
Parent::CallResult(results, AZStd::forward<Function>(func), AZStd::forward<Interface>(iface), AZStd::forward<InputArgs>(args)...);
}
template<class Function, class Interface, class... InputArgs>
static void Call(Function&& func, Interface&& iface, InputArgs&&... args)
{
AZ_ASSET_ATTACH_TO_SCOPE(iface);
Parent::Call(AZStd::forward<Function>(func), AZStd::forward<Interface>(iface), AZStd::forward<InputArgs>(args)...);
}
};
}
} // namespace AzFramework

@ -1,120 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Memory/HphaSchema.h>
#include <AzCore/Memory/SimpleSchemaAllocator.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
namespace Debug
{
struct AssetTrackingId;
}
}
namespace AZStd
{
// Declare hash specializations for types that need them; implementations will have to come after the classes are fully defined
template<>
struct hash<AZ::Debug::AssetTrackingId>
{
size_t operator()(const AZ::Debug::AssetTrackingId& id) const;
};
}
namespace AZ
{
namespace Debug
{
class AssetTrackingImpl;
// Custom allocator for the Analyzer that doesn't go through profiling tools and cannot be overridden
class AssetTrackingAllocator : public AZ::SimpleSchemaAllocator<AZ::HphaSchema, AZ::HphaSchema::Descriptor, false>
{
public:
AZ_TYPE_INFO(AssetTrackingAllocator, "{F6C08E92-559C-4153-9620-6A8491F78F10}");
using Base = AZ::SimpleSchemaAllocator<AZ::HphaSchema, AZ::HphaSchema::Descriptor, false>;
using Descriptor = Base::Descriptor;
AssetTrackingAllocator()
: Base("AssetTrackingAllocator", "Allocator for the AssetTracking")
{
DisableOverriding();
}
};
using AZStdAssetTrackingAllocator = AZ::AZStdAlloc<AssetTrackingAllocator>;
using AssetTrackingString = AZStd::basic_string<char, AZStd::char_traits<char>, AZStdAssetTrackingAllocator>;
template<typename Key, typename MappedType>
using AssetTrackingMap = AZStd::unordered_map<Key, MappedType, AZStd::hash<Key>, AZStd::equal_to<Key>, AZStdAssetTrackingAllocator>;
// ID for an asset that is hashable.
// Currently only contains one string identifier, but we may want to store a more sophisticated ID in the future.
struct AssetTrackingId
{
AssetTrackingId(const char* id) : m_id(id)
{
}
bool operator==(const AssetTrackingId& other) const
{
return m_id == other.m_id;
}
AssetTrackingString m_id;
};
// Primary information about an asset.
// Currently just contains the ID of the asset, but in the future may carry additional information about that asset (such as where in code it was initialized).
struct AssetPrimaryInfo
{
const AssetTrackingId* m_id;
};
// Base class for a node in the asset tree. Implemented by the template AssetTreeNode<>.
class AssetTreeNodeBase
{
public:
virtual ~AssetTreeNodeBase() = default;
virtual const AssetPrimaryInfo* GetAssetPrimaryInfo() const = 0;
virtual AssetTreeNodeBase* FindOrAddChild(const AssetTrackingId& id, const AssetPrimaryInfo* info) = 0;
};
// Base class for an asset tree. Implemented by the template AssetTree<>.
class AssetTreeBase
{
public:
virtual ~AssetTreeBase() = default;
virtual AssetTreeNodeBase& GetRoot() = 0;
};
// Base class for an asset allocation table. Implemented by the template AssetAllocationTable<>.
class AssetAllocationTableBase
{
public:
virtual ~AssetAllocationTableBase() = default;
virtual AssetTreeNodeBase* FindAllocation(void* ptr) const = 0;
};
}
}
///////////////////////////////////////////////////////////////////////////////
// Hash functions for map support
///////////////////////////////////////////////////////////////////////////////
inline size_t AZStd::hash<AZ::Debug::AssetTrackingId>::operator()(const AZ::Debug::AssetTrackingId& info) const
{
return AZStd::hash<AZ::Debug::AssetTrackingString>()(info.m_id);
}

@ -1,174 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Debug/AssetTrackingTypes.h>
#include <AzCore/std/containers/map.h>
namespace AZ
{
namespace Debug
{
// A node in the current asset state tree.
// Each thread maintains a stack of currently in-scope assets. As this stack changes the asset tree forms.
// The same asset may appear in multiple places in the tree, e.g. if asset A is a common asset loaded by both asset B and asset C, the tree may look like:
// Root -> B -> A
// \--> C -> A
template<typename AssetDataT>
class AssetTreeNode : public AssetTreeNodeBase
{
public:
AssetTreeNode(const AssetPrimaryInfo* primaryInfo = nullptr, AssetTreeNode* parent = nullptr) :
m_primaryinfo(primaryInfo),
m_parent(parent)
{
}
~AssetTreeNode() override = default;
const AssetPrimaryInfo* GetAssetPrimaryInfo() const override
{
return m_primaryinfo;
}
AssetTreeNodeBase* FindOrAddChild(const AssetTrackingId& id, const AssetPrimaryInfo* info) override
{
AssetTreeNodeBase* result = nullptr;
auto childItr = m_children.find(id);
if (childItr != m_children.end())
{
result = &childItr->second;
}
else
{
auto childResult = m_children.emplace(id, AssetTreeNode(info, this));
result = &childResult.first->second;
}
return result;
}
using AssetMap = AssetTrackingMap<AssetTrackingId, AssetTreeNode>;
const AssetPrimaryInfo* m_primaryinfo;
AssetTreeNode* m_parent;
AssetMap m_children;
AssetDataT m_data;
};
template<typename AssetDataT>
class AssetTree : public AssetTreeBase
{
public:
~AssetTree() override = default;
AssetTreeNodeBase& GetRoot() override
{
return m_rootAssets;
}
using NodeType = AssetTreeNode<AssetDataT>;
NodeType m_rootAssets;
};
template<typename AllocationDataT>
struct AllocationRecord
{
AssetTreeNodeBase* m_asset;
uint32_t m_size;
AllocationDataT m_data;
};
template<typename AllocationDataT>
class AllocationTable : public AssetAllocationTableBase
{
public:
using RecordType = AllocationRecord<AllocationDataT>;
using AllocationReverseMap = AZStd::map<void*, RecordType, AZStd::greater<void*>, AZStdAssetTrackingAllocator>;
using mutex_type = AZStd::mutex;
using lock_type = AZStd::lock_guard<mutex_type>;
AllocationTable(mutex_type& mutex) : m_mutex(mutex)
{
}
~AllocationTable() override = default;
AssetTreeNodeBase* FindAllocation(void* ptr) const override
{
// Note that ptr is not guaranteed to have an exact entry in the map. For instance, ptr may point to a member of the original object that was allocated, or
// ptr may be a different "this" pointer in the case of multiple inheritance.
//
// To solve this, we use lower_bound() and check to see if ptr falls in the range of the nearest allocation. Our map uses AZStd::greater instead of
// AZStd::less as its sorting function, and thus sorts largest-to-smallest instead of smallest-to-largest. This causes lower_bound() to return the first
// iterator that is not greater than otherAllocation, i.e. less than or equal to ptr.
lock_type lock(m_mutex);
auto itr = m_allocationTable.lower_bound(ptr);
AssetTreeNodeBase* result = nullptr;
if (itr != m_allocationTable.end())
{
// Check if otherAllocation is within the size range of the allocation we found
if (reinterpret_cast<uintptr_t>(ptr) <= reinterpret_cast<uintptr_t>(itr->first) + itr->second.m_size)
{
result = itr->second.m_asset;
}
}
return result;
}
void ReallocateAllocation(void* prevAddress, void* newAddress, size_t newByteSize)
{
lock_type lock(m_mutex);
auto itr = m_allocationTable.find(prevAddress);
if (itr != m_allocationTable.end())
{
RecordType newAllocation = itr->second;
newAllocation.m_size = (uint32_t)newByteSize;
m_allocationTable.erase(itr);
m_allocationTable.emplace(newAddress, AZStd::move(newAllocation));
}
}
void ResizeAllocation(void* address, size_t newSize)
{
// Resize an existing allocation if we can find it
lock_type lock(m_mutex);
auto itr = m_allocationTable.find(address);
if (itr != m_allocationTable.end())
{
itr->second.m_size = (uint32_t)newSize;
}
}
AllocationReverseMap& Get()
{
return m_allocationTable;
}
const AllocationReverseMap& Get() const
{
return m_allocationTable;
}
private:
AllocationReverseMap m_allocationTable;
mutex_type& m_mutex;
};
}
}

@ -1,29 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/Debug/EventTraceDrillerBus.h>
#include <AzCore/std/time.h>
#include <AzCore/std/parallel/thread.h>
namespace AZ::Debug
{
EventTrace::ScopedSlice::ScopedSlice(const char* name, const char* category)
: m_Name(name)
, m_Category(category)
, m_Time(AZStd::GetTimeNowMicroSecond())
{
}
EventTrace::ScopedSlice::~ScopedSlice()
{
EventTraceDrillerBus::TryQueueBroadcast(
&EventTraceDrillerInterface::RecordSlice, m_Name, m_Category, AZStd::this_thread::get_id(), m_Time,
(uint32_t)(AZStd::GetTimeNowMicroSecond() - m_Time));
}
} // namespace AZ::Debug

@ -1,43 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/PlatformDef.h>
#include <AzCore/Debug/Profiler.h>
namespace AZStd
{
struct thread_id;
}
namespace AZ
{
namespace Debug
{
namespace EventTrace
{
class ScopedSlice
{
public:
ScopedSlice(const char* name, const char* category);
~ScopedSlice();
private:
const char* m_Name;
const char* m_Category;
u64 m_Time;
};
}
}
}
#define AZ_TRACE_METHOD_NAME_CATEGORY(name, category)
#define AZ_TRACE_METHOD_NAME(name) AZ_TRACE_METHOD_NAME_CATEGORY(name, "")
#define AZ_TRACE_METHOD() AZ_TRACE_METHOD_NAME(AZ_FUNCTION_SIGNATURE)

@ -1,159 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Debug/EventTraceDriller.h>
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/std/containers/array.h>
#include <algorithm>
namespace AZ::Debug
{
namespace Crc
{
constexpr u32 EventTraceDriller = AZ_CRC_CE("EventTraceDriller");
constexpr u32 Slice = AZ_CRC_CE("Slice");
constexpr u32 ThreadInfo = AZ_CRC_CE("ThreadInfo");
constexpr u32 Name = AZ_CRC_CE("Name");
constexpr u32 Category = AZ_CRC_CE("Category");
constexpr u32 ThreadId = AZ_CRC_CE("ThreadId");
constexpr u32 Timestamp = AZ_CRC_CE("Timestamp");
constexpr u32 Duration = AZ_CRC_CE("Duration");
constexpr u32 Instant = AZ_CRC_CE("Instant");
}
EventTraceDriller::EventTraceDriller()
{
EventTraceDrillerSetupBus::Handler::BusConnect();
AZStd::ThreadDrillerEventBus::Handler::BusConnect();
}
EventTraceDriller::~EventTraceDriller()
{
AZStd::ThreadDrillerEventBus::Handler::BusDisconnect();
EventTraceDrillerSetupBus::Handler::BusDisconnect();
}
void EventTraceDriller::Start(const Param* params, int numParams)
{
(void)params;
(void)numParams;
EventTraceDrillerBus::Handler::BusConnect();
TickBus::Handler::BusConnect();
EventTraceDrillerBus::AllowFunctionQueuing(true);
}
void EventTraceDriller::Stop()
{
EventTraceDrillerBus::AllowFunctionQueuing(false);
EventTraceDrillerBus::ClearQueuedEvents();
EventTraceDrillerBus::Handler::BusDisconnect();
TickBus::Handler::BusDisconnect();
}
void EventTraceDriller::OnTick(float deltaTime, ScriptTimePoint time)
{
(void)deltaTime;
(void)time;
AZ_TRACE_METHOD();
RecordThreads();
EventTraceDrillerBus::ExecuteQueuedEvents();
}
void EventTraceDriller::SetThreadName(const AZStd::thread_id& id, const char* name)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_ThreadMutex);
m_Threads[(size_t)id.m_id] = ThreadData{ name };
}
void EventTraceDriller::OnThreadEnter(const AZStd::thread::id& id, const AZStd::thread_desc* desc)
{
if (desc && desc->m_name)
{
SetThreadName(id, desc->m_name);
}
}
void EventTraceDriller::OnThreadExit(const AZStd::thread::id& id)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_ThreadMutex);
m_Threads.erase((size_t)id.m_id);
}
void EventTraceDriller::RecordThreads()
{
if (!m_output || m_Threads.empty())
{
return;
}
// Main bus mutex guards m_output.
auto& context = EventTraceDrillerBus::GetOrCreateContext();
AZStd::scoped_lock<decltype(context.m_contextMutex), decltype(m_ThreadMutex)> lock(context.m_contextMutex, m_ThreadMutex);
for (const auto& keyValue : m_Threads)
{
m_output->BeginTag(Crc::EventTraceDriller);
m_output->BeginTag(Crc::ThreadInfo);
m_output->Write(Crc::ThreadId, keyValue.first);
m_output->Write(Crc::Name, keyValue.second.name);
m_output->EndTag(Crc::ThreadInfo);
m_output->EndTag(Crc::EventTraceDriller);
}
}
void EventTraceDriller::RecordSlice(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp,
AZ::u32 duration)
{
m_output->BeginTag(Crc::EventTraceDriller);
m_output->BeginTag(Crc::Slice);
m_output->Write(Crc::Name, name);
m_output->Write(Crc::Category, category);
m_output->Write(Crc::ThreadId, (size_t)threadId.m_id);
m_output->Write(Crc::Timestamp, timestamp);
m_output->Write(Crc::Duration, std::max(duration, 1u));
m_output->EndTag(Crc::Slice);
m_output->EndTag(Crc::EventTraceDriller);
}
void EventTraceDriller::RecordInstantGlobal(
const char* name,
const char* category,
AZ::u64 timestamp)
{
m_output->BeginTag(Crc::EventTraceDriller);
m_output->BeginTag(Crc::Instant);
m_output->Write(Crc::Name, name);
m_output->Write(Crc::Category, category);
m_output->Write(Crc::Timestamp, timestamp);
m_output->EndTag(Crc::Instant);
m_output->EndTag(Crc::EventTraceDriller);
}
void EventTraceDriller::RecordInstantThread(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp)
{
m_output->BeginTag(Crc::EventTraceDriller);
m_output->BeginTag(Crc::Instant);
m_output->Write(Crc::Name, name);
m_output->Write(Crc::Category, category);
m_output->Write(Crc::ThreadId, (size_t)threadId.m_id);
m_output->Write(Crc::Timestamp, timestamp);
m_output->EndTag(Crc::Instant);
m_output->EndTag(Crc::EventTraceDriller);
}
} // namespace AZ::Debug

@ -1,87 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Driller/Driller.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/parallel/threadbus.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/Debug/EventTraceDrillerBus.h>
namespace AZ
{
namespace Debug
{
class EventTraceDriller
: public Driller
, public EventTraceDrillerBus::Handler
, public EventTraceDrillerSetupBus::Handler
, public AZStd::ThreadDrillerEventBus::Handler
, public AZ::TickBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(EventTraceDriller, OSAllocator, 0)
EventTraceDriller();
virtual ~EventTraceDriller();
private:
// Driller
//////////////////////////////////////////////////////////////////////////
const char* GroupName() const override { return "SystemDrillers"; }
const char* GetName() const override { return "EventTraceDriller"; }
const char* GetDescription() const override { return "Handles timed events for a Chrome Tracing."; }
void Start(const Param* params = NULL, int numParams = 0) override;
void Stop() override;
// ThreadBus
//////////////////////////////////////////////////////////////////////////
void OnThreadEnter(const AZStd::thread::id& id, const AZStd::thread_desc* desc) override;
void OnThreadExit(const AZStd::thread::id& id) override;
// TickBus
//////////////////////////////////////////////////////////////////////////
void OnTick(float deltaTime, ScriptTimePoint time) override;
// EventTraceDrillerSetupBus
//////////////////////////////////////////////////////////////////////////
void SetThreadName(const AZStd::thread_id& threadId, const char* name) override;
// EventTraceDrillerBus
//////////////////////////////////////////////////////////////////////////
void RecordSlice(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp,
AZ::u32 duration) override;
void RecordInstantGlobal(
const char* name,
const char* category,
AZ::u64 timestamp) override;
void RecordInstantThread(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp) override;
void RecordThreads();
struct ThreadData
{
AZStd::string name;
};
AZStd::recursive_mutex m_ThreadMutex;
AZStd::unordered_map<size_t, ThreadData, AZStd::hash<size_t>, AZStd::equal_to<size_t>, OSStdAllocator> m_Threads;
};
}
} // namespace AZ

@ -1,81 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Driller/DrillerBus.h>
#include <AzCore/std/time.h>
#include <AzCore/std/string/string.h>
namespace AZStd
{
struct thread_id;
}
namespace AZ
{
namespace Debug
{
class EventTraceDrillerInterface
: public DrillerEBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const bool EnableEventQueue = true;
static const bool EventQueueingActiveByDefault = false;
//////////////////////////////////////////////////////////////////////////
virtual ~EventTraceDrillerInterface() {}
virtual void RecordSlice(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp,
AZ::u32 duration) = 0;
virtual void RecordInstantThread(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp) = 0;
virtual void RecordInstantGlobal(
const char* name,
const char* category,
AZ::u64 timestamp) = 0;
};
typedef AZ::EBus<EventTraceDrillerInterface> EventTraceDrillerBus;
class EventTraceDrillerSetupInterface
: public DrillerEBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//////////////////////////////////////////////////////////////////////////
virtual ~EventTraceDrillerSetupInterface() {}
virtual void SetThreadName(const AZStd::thread_id& threadId, const char* name) = 0;
};
typedef AZ::EBus<EventTraceDrillerSetupInterface> EventTraceDrillerSetupBus;
}
}
#define AZ_TRACE_INSTANT_GLOBAL_CATEGORY(name, category) \
EBUS_QUEUE_EVENT(AZ::Debug::EventTraceDrillerBus, RecordInstantGlobal, name, category, AZStd::GetTimeNowMicroSecond())
#define AZ_TRACE_INSTANT_GLOBAL(name) AZ_TRACE_INSTANT_GLOBAL_CATEGORY(name, "")
#define AZ_TRACE_INSTANT_THREAD_CATEGORY(name, category) \
EBUS_QUEUE_EVENT(AZ::Debug::EventTraceDrillerBus, RecordInstantThread, name, category, AZStd::this_thread::get_id(), AZStd::GetTimeNowMicroSecond())
#define AZ_TRACE_INSTANT_THREAD(name) AZ_TRACE_INSTANT_THREAD_CATEGORY(name, "")

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

Loading…
Cancel
Save