Merge branch 'development' into memory/benchmarks
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>monroegm-disable-blank-issue-2
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))
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -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()
|
||||
@ -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
|
||||
@ -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)
|
||||
@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7c6b33c6137d6bd8c696f180c30a23089c95c1af398a630b4b13e080bec3254d
|
||||
size 18220
|
||||
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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
|
||||
@ -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…
Reference in New Issue